一维相位去包裹:原理与仿真

Off-Topic

未觉池塘春草色,阶前梧叶已秋声。

——朱熹

时光飞逝,转眼就要毕业了。前天导师打电话摧我,这几个月就毕业了,你那毕业设计得赶紧做了。想想真是,四年倏忽过去,浑然不觉,已近毕业。

也许这是最后自在平静的日子了,时间却像沙子,越用力抓紧,从指间溜走地越快。

两年过得太快,I follow my passion,做了太多没用的事,有得有失。只是希望:

You can’t connect the dots looking forward you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future. You have to trust in something: your gut, destiny, life, karma, whatever. Because believing that the dots will connect down the road will give you the confidence to follow your heart, even when it leads you off the well worn path.

——Steve Jobs, Stanford Commencement Adress, 2005

为何相位重要

我大学的专业是光学,时至今日,依然清晰记得两年前在阴暗的实验室做全息摄像的实验。我们几个同学花了好久把光路摆好,让激光打在物体上反光到干版上曝光。然后拿到什么试剂中定形。最后在拿出来用激光将物体的影响重新清晰的放出三维的像时,心里相当兴奋。

为什么全息照相能产生立体的像呢?普通的照相技术都是仅仅记录光波的强度,而不记录相位,因此失去了很多相位中的信息。但全息照相通过相干光(激光)之间的干涉在干版上同时记录下了强度和相位,再用相干光照射干版重放,好像光是从真的物体发出的一样。

如果您也做过这种实验,您应该知道为什么相位重要了。

这还有另一个例子,关于爱因斯坦和蒙娜丽莎。

我从《Two Dimensional Phase Unwrapping Theory Algorithms and Software》中看到了这个例子,然后自己动手用python试了试。

以下代码用来交换两个图片的相位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# -*- coding: utf-8 -*-
# <nbformat>3.0</nbformat>

# <codecell>

from scipy.fftpack import *

# <codecell>

# read file
im_1 = plt.imread('einstein.jpg')
im_2 = plt.imread('monalisa.jpg')

# fft and reverse two images' phase
m_1, p_1 = np.abs(fft2(im_1)), np.angle(fft2(im_1))
m_2, p_2 = np.abs(fft2(im_2)), np.angle(fft2(im_2))

# <codecell>

im_swapphase_1 = np.real(ifft2(m_1 * np.cos(p_2) + m_1 * np.sin(p_2) * 1j))
im_swapphase_2 = np.real(ifft2(m_2 * np.cos(p_1) + m_2 * np.sin(p_1) * 1j))

# <codecell>

plt.figsize(10,10)
plt.gray()
plt.subplot(2,2,1)
plt.imshow(im_1, origin='lower')
plt.subplot(2,2,2)
plt.imshow(im_2, origin='lower')
plt.subplot(2,2,3)
plt.imshow(im_swapphase_1, origin='lower')
plt.subplot(2,2,4)
plt.imshow(im_swapphase_2, origin='lower')

上面两幅图是交换相位前的图,下面两幅是之后的。显然,一团糟,相位中是有信息的。

Einstein and Monalisa

为何要相位去包裹

简单地说,就是说,任何仪器,比如说量角器,顶多只能测得($-\pi, \pi$]之间的量,但真正的相位角度不该这样,而是分布在实数空间内,应该是测得的($-\pi, \pi$]之间的值的2$\pi$整数倍。

真正的相位值被“包裹”起来了,但为什么要解包裹呢?

不解包裹无法对相位进行计算。

Itoh的路径积分法

我们先定义一个获取包裹相位的算子$\mathscr{W}$,该算子将相位包裹,获取位于$(-\pi,\pi]$之间的包裹相位。

$$\mathscr{W}\varphi = \arctan[\cos(Real \varphi / Img \varphi)]$$

还可以这样写

$$\mathscr{W}{\varphi(n)} = \psi(n) = \varphi(n) + 2\pi k(n)$$

其中$k(n)$是使包裹相位位于$-\pi,\pi$之间的值。

显然包裹相位$\psi(n)$有:

$$\pi \geq \psi(n) \gt -\pi$$

定义差分算子$\Delta$:

$$\Delta {\varphi(n)} = \varphi(n+1) - \varphi(n)$$

$$\Delta {k(n)} = k(n+1) - k(n)$$

计算被包裹相位的差分:

$$\Delta {\psi(n)} = \Delta {\varphi(n)} + 2\pi\Delta {k_1(n)}$$

我们再用$\mathscr{W}$作用于该差分得:

$$\mathscr{W}{\Delta {\psi(n)}} = \Delta {\varphi(n)} + 2\pi[\Delta {k_1(n)} + k_2(n)]$$

显然上式结果应该位于$(-\pi,\pi]$,假如此时还有$\Delta {\varphi(n)}$也位于$(-\pi,\pi]$,则上式右边第二项$2\pi[\Delta {k_1(n) } + k_2(n)]$应该为零,则有:

$$\Delta {\varphi(n)} = \mathscr{W}{\Delta {\psi(n)}} $$

显然,由该差分式可得:

$$\varphi(m) = \varphi(0) + \sum_{n=0}^{m-1} \mathscr{W}{\Delta {\mathscr{W}{\varphi(n)}}} $$

上式说明,真实相位可以通过对包裹相位的差分的包裹进行积分求得。

于是itoh的一维相位去包裹算法综述如下:

对信号相位数组$\psi(i),0 \leq i \leq N-1$

  • 计算相位差分$D(i) = \psi(i+1)-\psi(i), i=0,\ldots,N-2$
  • 计算包裹的相位差分$\Delta(i) = \Delta{D(i)}, i=0,\ldots,N-2$
  • 初始化初值$\varphi(0)= \psi(0)$
  • 累加解包裹$\varphi(i) = \psi(i) + \Delta(i)$

Itoh的方法很简单实用,但受到两个重要因素的影响:相位失真和噪声。下面仿真两种情况的影响。

仿真

对正弦波相位函数(间谐波,一切波的基础)

$$\varphi(t) = 10\sin(10t), 0 \leq t \leq 1$$

通过计算可以得知,使之不产生相位失真,区间内至少有32个采样点:

对相位变化有

$$\Delta \varphi = \dot{\varphi}\Delta t$$

其中 $\dot{\varphi} = d\varphi / dt = 100\cos10t$ ,可看出相位在 $n\pi/10$ 取得极值,加上条件:

$$\left\vert\Delta \varphi\right\vert \lt \pi$$

$$\left\vert \frac{100}{N} \cos 10t \right\vert \lt \pi$$

$$N \gt 31.83$$

则对这个函数如果采样率低于32就会产生相位失真。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# -*- coding: utf-8 -*-
# <nbformat>3.0</nbformat>

# <codecell>

import numpy as np
import matplotlib.pyplot as plt

# <codecell>

# phase function
def pf(x):
return 10 * np.sin(10 * x)
# wrap function
def wrap(x):
return np.arctan2(np.sin(x), np.cos(x))
# unwrap function
def wrap_diff(x):
return wrap(np.diff(x))

def unwrap(x):
y = x
y[0] = x[0]
for i in range(len(x) - 1):
i += 1
y[i] = y[i - 1] + wrap_diff(x)[i - 1]
return np.array(y)

def noise(x, snr):
return x + np.random.normal(loc=0.0, scale = np.sqrt(np.max(x) / 2 ** snr), size=len(x))

def show_unwrap(x,y,y_w,t,p):
plt.ylim((-12,12))
plt.ylabel("Phase in Radians")
p_w, = plt.plot(x, y_w,'o:')
p_o, = plt.plot(t,p,':')
p_u, = plt.plot(x,y,'s')
plt.legend([p_w, p_o, p_u], ["sampled wrapped phase", "original phase function", "unwrapped phase"])


# <headingcell level=1>

# 当取样点为num时

# <codecell>

# Origin
t = np.arange(0,1,0.01)
p = pf(t)

# plot
## num 50
plt.subplot(311)
plt.title("num = 50")
# sampled data
num = 50
x = np.linspace(0,1,num)
# wrapped phase
y_w = wrap(pf(x))
# unwrapped wraped phase
y = unwrap(wrap(pf(x)))
show_unwrap(x, y, y_w, t, p)
## num = 32
plt.subplot(312)
plt.title("num = 32")
# sampled data
num = 32
x = np.linspace(0,1,num)
# wrapped phase
y_w = wrap(pf(x))
# unwrapped wraped phase
y = unwrap(wrap(pf(x)))
show_unwrap(x, y, y_w, t, p)
## num = 31
plt.subplot(313)
plt.title("num = 31")
# sampled data
num = 31
x = np.linspace(0,1,num)
# wrapped phase
y_w = wrap(pf(x))
# unwrapped wraped phase
y = unwrap(wrap(pf(x)))
show_unwrap(x, y, y_w, t, p)
plt.xlabel("Relative Time")


# <headingcell level=1>

# 噪声

# <codecell>

# noise influence
x = np.linspace(0,1,200)
y = pf(x)
y_10 = unwrap(wrap(noise(y,10))) + 20
y_5 = unwrap(noise(wrap(y), 5)) + 40
y_2 = unwrap(noise(wrap(y), 2)) + 60
y_1 = unwrap(noise(wrap(y), 1)) + 80
# plot
plt.xlabel("Relative Time")
plt.ylabel("Phase in Radians")
p_o, = plt.plot(t,p)
p_1, = plt.plot(x,y_1,'--')
p_2, = plt.plot(x,y_2,'-.')
p_5, = plt.plot(x,y_5,':')
p_10, = plt.plot(x,y_10,':')
plt.legend([p_o, p_1, p_2, p_5, p_10], ["Origin", "SNR=1", "SNR=2", "SNR=5", "SNR=10"])

# <codecell>

我们可以看到:

  • 在采样率低于某一关键值32时基本没法解出正确相位。

    phase aliasing

  • 信噪比越低,解包裹效果越差。
    noise

Summary

如果是二维相位去包裹问题,还有个奇点的问题。噪音、相位失真、奇点,似乎是所有相位解缠算法必须面对的三大问题。

相关Ipython Notebook文件和Einstein和Monalisa的图像在此下载

高级Python结构

原谅渣翻译,可能仅仅是给我自己看的。本来多年(也就几个月吧)之前将此文投递到OSChina翻译频道结果被以代码太多为由被拒,于是译者只好用自己的渣英语渣水平翻译给自己看了……,期待各路大婶们指正……

翻译自http://scipy-lectures.github.com/advanced/advanced_python/index.html

作者: Zbigniew Jędrzejewski-Szmek

这章有关Python中被认为高级的特性——就是说并不是每个语言都有的,也是说它们可能在更复杂的程序或库中更有用,但不是说特别特殊或特别复杂。

强调这点很重要:这一章仅仅关于语言自身——关于辅之以Python的标准库功能的特殊语法所支持的特性,不包括那些智能的外部模块实现。

在开发Python程序语言的过程中,它的语法,独一无二。因为它非常透明。建议的更改通过不同的角度评估并在公开邮件列表讨论,最终决定考虑到假设用例的重要性、添加更多特性的负担,其余语法的一致性、是否建议的变种易于读写和理解之间的平衡。这个过程由Python Enhancement Proposals(PEPs)的形式规范。最终这一章节中描述的特性在证明它们确实解决实际问题并且使用起来尽可能简单后被添加。


目录

  • toc
    {: toc}

迭代器(Iterators), 生成表达式(generator expressions)和生成器(generators)

迭代器

简单

重复工作是浪费,将不同“土生土长”的方法替换为标准特性换来的是更加易于阅读和操作。

Guido van Rossum — Adding Optional Static Typing to Python

迭代器是依附于迭代协议的对象——基本意味它有一个next方法(method),当调用时,返回序列中的下一个项目。当无项目可返回时,引发(raise)StopIteration异常。

迭代对象允许一次循环。它保留单次迭代的状态(位置),或从另一个角度讲,每次循环序列都需要一个迭代对象。这意味我们可以同时迭代同一个序列不只一次。将迭代逻辑和序列分离使我们有更多的迭代方式。

调用一个容器(container)的__iter__方法创建迭代对象是掌握迭代器最直接的方式。iter函数为我们节约一些按键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> nums = [1,2,3]      # note that ... varies: these are different objects
>>> iter(nums)
<listiterator object at ...>
>>> nums.__iter__()
<listiterator object at ...>
>>> nums.__reversed__()
<listreverseiterator object at ...>

>>> it = iter(nums)
>>> next(it) # next(obj) simply calls obj.next()
1
>>> it.next()
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

当在循环中使用时,StopIteration被接受并停止循环。但通过显式引发(invocation),我们看到一旦迭代器元素被耗尽,存取它将引发异常。

使用for...in循环也使用__iter__方法。这允许我们透明地开始对一个序列迭代。但是如果我们已经有一个迭代器,我们想在for循环中能同样地使用它们。为了实现这点,迭代器除了next还有一个方法__iter__来返回迭代器自身(self)。

Python中对迭代器的支持无处不在:标准库中的所有序列和无序容器都支持。这个概念也被拓展到其它东西:例如file对象支持行的迭代。

1
2
3
>>> f = open('/etc/fstab')
>>> f is f.__iter__()
True

file自身就是迭代器,它的__iter__方法并不创建一个单独的对象:仅仅单线程的顺序读取被允许。

生成表达式

第二种创建迭代对象的方式是通过 生成表达式(generator expression) ,列表推导(list comprehension)的基础。为了增加清晰度,生成表达式总是封装在括号或表达式中。如果使用圆括号,则创建了一个生成迭代器(generator iterator)。如果是方括号,这一过程被‘短路’我们获得一个列表list

1
2
3
4
5
6
>>> (i for i in nums)                    
<generator object <genexpr> at 0x...>
>>> [i for i in nums]
[1, 2, 3]
>>> list(i for i in nums)
[1, 2, 3]

在Python 2.7和 3.x中列表表达式语法被扩展到 字典和集合表达式。一个集合set当生成表达式是被大括号封装时被创建。一个字典dict在表达式包含key:value形式的键值对时被创建:

1
2
3
4
>>> {i for i in range(3)}   
set([0, 1, 2])
>>> {i:i**2 for i in range(3)}
{0: 0, 1: 1, 2: 4}

如果您不幸身陷古老的Python版本中,这个语法有点糟:

1
2
3
4
>>> set(i for i in 'abc')
set(['a', 'c', 'b'])
>>> dict((i, ord(i)) for i in 'abc')
{'a': 97, 'c': 99, 'b': 98}

生成表达式相当简单,不用多说。只有一个陷阱值得提及:在版本小于3的Python中索引变量(i)会泄漏。

生成器

生成器

生成器是产生一列结果而不是单一值的函数。

David Beazley — A Curious Course on Coroutines and Concurrency

第三种创建迭代对象的方式是调用生成器函数。一个 生成器(generator) 是包含关键字yield的函数。值得注意,仅仅是这个关键字的出现完全改变了函数的本质:yield语句不必引发(invoke),甚至不必可接触。但让函数变成了生成器。当一个函数被调用时,其中的指令被执行。而当一个生成器被调用时,执行在其中第一条指令之前停止。生成器的调用创建依附于迭代协议的生成器对象。就像常规函数一样,允许并发和递归调用。

next被调用时,函数执行到第一个yield。每次遇到yield语句获得一个作为next返回的值,在yield语句执行后,函数的执行又被停止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> def f():
... yield 1
... yield 2
>>> f()
<generator object f at 0x...>
>>> gen = f()
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

让我们遍历单个生成器函数调用的整个历程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> def f():
... print("-- start --")
... yield 3
... print("-- middle --")
... yield 4
... print("-- finished --")
>>> gen = f()
>>> next(gen)
-- start --
3
>>> next(gen)
-- middle --
4
>>> next(gen)
-- finished --
Traceback (most recent call last):
...
StopIteration

相比常规函数中执行f()立即让print执行,gen不执行任何函数体中语句就被赋值。只有当gen.next()next调用,直到第一个yield部分的语句才被执行。第二个语句打印-- middle --并在遇到第二个yield时停止执行。第三个next打印-- finished --并且到函数末尾,因为没有yield,引发了异常。

当函数yield之后控制返回给调用者后发生了什么?每个生成器的状态被存储在生成器对象中。从这点看生成器函数,好像它是运行在单独的线程,但这仅仅是假象:执行是严格单线程的,但解释器保留和存储在下一个值请求之间的状态。^1

为何生成器有用?正如关于迭代器这部分强调的,生成器函数只是创建迭代对象的又一种方式。一切能被yield语句完成的东西也能被next方法完成。然而,使用函数让解释器魔力般地创建迭代器有优势。一个函数可以比需要next__iter__方法的类定义短很多。更重要的是,相比不得不对迭代对象在连续next调用之间传递的实例(instance)属性来说,生成器的作者能更简单的理解局限在局部变量中的语句。

还有问题是为何迭代器有用?当一个迭代器用来驱动循环,循环变得简单。迭代器代码初始化状态,决定是否循环结束,并且找到下一个被提取到不同地方的值。这凸显了循环体——最值得关注的部分。除此之外,可以在其它地方重用迭代器代码。

双向通信

每个yield语句将一个值传递给调用者。这就是为何PEP 255引入生成器(在Python2.2中实现)。但是相反方向的通信也很有用。一个明显的方式是一些外部(extern)语句,或者全局变量或共享可变对象。通过将先前无聊的yield语句变成表达式,直接通信因PEP 342成为现实(在2.5中实现)。当生成器在yield语句之后恢复执行时,调用者可以对生成器对象调用一个方法,或者传递一个值 生成器,然后通过yield语句返回,或者通过一个不同的方法向生成器注入异常。

第一个新方法是send(value),类似于next(),但是将value传递进作为yield表达式值的生成器中。事实上,g.next()g.send(None)是等效的。

第二个新方法是throw(type, value=None, traceback=None),等效于在yield语句处

1
raise type, value, traceback

不像raise(从执行点立即引发异常),throw()首先恢复生成器,然后仅仅引发异常。选用单次throw就是因为它意味着把异常放到其它位置,并且在其它语言中与异常有关。

当生成器中的异常被引发时发生什么?它可以或者显式引发,当执行某些语句时可以通过throw()方法注入到yield语句中。任一情况中,异常都以标准方式传播:它可以被exceptfinally捕获,或者造成生成器的中止并传递给调用者。

因完整性缘故,值得提及生成器迭代器也有close()方法,该方法被用来让本可以提供更多值的生成器立即中止。它用生成器的__del__方法销毁保留生成器状态的对象。

让我们定义一个只打印出通过send和throw方法所传递东西的生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>> import itertools
>>> def g():
... print '--start--'
... for i in itertools.count():
... print '--yielding %i--' % i
... try:
... ans = yield i
... except GeneratorExit:
... print '--closing--'
... raise
... except Exception as e:
... print '--yield raised %r--' % e
... else:
... print '--yield returned %s--' % ans

>>> it = g()
>>> next(it)
--start--
--yielding 0--
0
>>> it.send(11)
--yield returned 11--
--yielding 1--
1
>>> it.throw(IndexError)
--yield raised IndexError()--
--yielding 2--
2
>>> it.close()
--closing--

注意: next还是__next__?

在Python 2.x中,接受下一个值的迭代器方法是next,它通过全局函数next显式调用,意即它应该调用__next__。就像全局函数iter调用__iter__。这种不一致在Python 3.x中被修复,it.next变成了it.__next__。对于其它生成器方法——sendthrow情况更加复杂,因为它们不被解释器隐式调用。然而,有建议语法扩展让continue带一个将被传递给循环迭代器中send的参数。如果这个扩展被接受,可能gen.send会变成gen.__send__。最后一个生成器方法close显然被不正确的命名了,因为它已经被隐式调用。

链式生成器

注意: 这是PEP 380的预览(还未被实现,但已经被Python3.3接受)[^2]

比如说我们正写一个生成器,我们想要yield一个第二个生成器——一个子生成器(subgenerator)——生成的数。如果仅考虑产生(yield)的值,通过循环可以不费力的完成:

1
2
3
subgen = some_other_generator()
for v in subgen:
yield v

然而,如果子生成器需要调用send()throw()close()和调用者适当交互的情况下,事情就复杂了。yield语句不得不通过类似于前一章节部分定义的try...except...finally结构来保证“调试”生成器函数。这种代码在PEP 380中提供,现在足够拿出将在Python 3.3中引入的新语法了:

1
yield from some_other_generator()

像上面的显式循环调用一样,重复从some_other_generator中产生值直到没有值可以产生,但是仍然向子生成器转发sendthrowclose

装饰器

总结

这个语言中令人激动的特性几乎充满歉意的,考虑到它可能没这么有用。

Bruce Eckel — An Introduction to Python Decorators

因为函数或类都是对象,它们也能被四处传递。它们又是可变对象,可以被更改。在函数或类对象创建后但绑定到名字前更改之的行为为装饰(decorator)。^4

“装饰器”后隐藏了两种意思——一是函数起了装饰作用,例如,执行真正的工作,另一个是依附于装饰器语法的表达式,例如,at符号和装饰函数的名称。

函数可以通过函数装饰器语法装饰:

1
2
3
@decorator             # ②
def function(): # ①
pass
  • 函数以标准方式定义。①
  • @做为定义为装饰器函数前缀的表达式②。在 @ 后的部分必须是简单的表达式,通常只是函数或类的名字。这一部分先求值,在下面的定义的函数准备好后,装饰器被新定义的函数对象作为单个参数调用。装饰器返回的值附着到被装饰的函数名。

装饰器可以应用到函数和类上。对类语义很明晰——类定义被当作参数来调用装饰器,无论返回什么都赋给被装饰的名字。

在装饰器语法实现前(PEP 318),通过将函数和类对象赋给临时变量然后显式调用装饰器然后将返回值赋给函数名,可以完成同样的事。这似乎要打更多的字,也确实装饰器函数名用了两次同时临时变量要用至少三次,很容易出错。以上实例相当于:

1
2
3
def function():                  # ①
pass
function = decorator(function) # ②

装饰器可以堆栈(stacked)——应用的顺序是从底到上或从里到外。就是说最初的函数被当作第一次参数器的参数,无论返回什么都被作为第二个装饰器的参数……无论最后一个装饰器返回什么都被依附到最初函数的名下。

装饰器语法因其可读性被选择。因为装饰器在函数头部前被指定,显然不是函数体的一部分,它只能对整个函数起作用。以@为前缀的表达式又让它明显到不容忽视(根据PEP叫在您脸上……:))。当多个装饰器被应用时,每个放在不同的行非常易于阅读。

代替和调整原始对象

装饰器可以或者返回相同的函数或类对象或者返回完全不同的对象。第一种情况中,装饰器利用函数或类对象是可变的添加属性,例如向类添加文档字符串(docstring).装饰器甚至可以在不改变对象的情况下做有用的事,例如在全局注册表中注册装饰的类。在第二种情况中,简直无所不能:当什么不同的东西取代了被装饰的类或函数,新对象可以完全不同。然而这不是装饰器的目的:它们意在改变装饰对象而非做不可预料的事。因此当一个函数在装饰时被完全替代成不同的函数时,新函数通常在一些准备工作后调用原始函数。同样,当一个类被装饰成一个新类时,新类通常源于被装饰类。当装饰器的目的是“每次都”做什么,像记录每次对被装饰函数的调用,只有第二类装饰器可用。另一方面,如果第一类足够了,最好使用它因为更简单。^3

实现类和函数装饰器

对装饰器惟一的要求是它能够单参数调用。这意味着装饰器可以作为常规函数或带有__call__方法的类的实现,理论上,甚至lambda函数也行。

让我们比较函数和类方法。装饰器表达式(@后部分)可以只是名字。只有名字的方法很好(打字少,看起来整洁等),但是只有当无需用参数定制装饰器时才可能。被写作函数的装饰器可以用以下两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> def simple_decorator(function):
... print "doing decoration"
... return function
>>> @simple_decorator
... def function():
... print "inside function"
doing decoration
>>> function()
inside function

>>> def decorator_with_arguments(arg):
... print "defining the decorator"
... def _decorator(function):
... # in this inner function, arg is available too
... print "doing decoration,", arg
... return function
... return _decorator
>>> @decorator_with_arguments("abc")
... def function():
... print "inside function"
defining the decorator
doing decoration, abc
>>> function()
inside function

这两个装饰器属于返回被装饰函数的类别。如果它们想返回新的函数,需要额外的嵌套,最糟的情况下,需要三层嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> def replacing_decorator_with_args(arg):
... print "defining the decorator"
... def _decorator(function):
... # in this inner function, arg is available too
... print "doing decoration,", arg
... def _wrapper(*args, **kwargs):
... print "inside wrapper,", args, kwargs
... return function(*args, **kwargs)
... return _wrapper
... return _decorator
>>> @replacing_decorator_with_args("abc")
... def function(*args, **kwargs):
... print "inside function,", args, kwargs
... return 14
defining the decorator
doing decoration, abc
>>> function(11, 12)
inside wrapper, (11, 12) {}
inside function, (11, 12) {}
14

_wrapper函数被定义为接受所有位置和关键字参数。通常我们不知道哪些参数被装饰函数会接受,所以wrapper将所有东西都创递给被装饰函数。一个不幸的结果就是显式参数很迷惑人。

相比定义为函数的装饰器,定义为类的复杂装饰器更简单。当对象被创建,__init__方法仅仅允许返回None,创建的对象类型不能更改。这意味着当装饰器被定义为类时,使用无参数的形式没什么意义:最终被装饰的对象只是装饰类的一个实例而已,被构建器(constructor)调用返回,并不非常有用。讨论在装饰表达式中给出参数的基于类的装饰器,__init__方法被用来构建装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> class decorator_class(object):
... def __init__(self, arg):
... # this method is called in the decorator expression
... print "in decorator init,", arg
... self.arg = arg
... def __call__(self, function):
... # this method is called to do the job
... print "in decorator call,", self.arg
... return function
>>> deco_instance = decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
... print "in function,", args, kwargs
in decorator call, foo
>>> function()
in function, () {}

相对于正常规则(PEP 8)由类写成的装饰器表现得更像函数,因此它们的名字以小写字母开始。

事实上,创建一个仅返回被装饰函数的新类没什么意义。对象应该有状态,这种装饰器在装饰器返回新对象时更有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> class replacing_decorator_class(object):
... def __init__(self, arg):
... # this method is called in the decorator expression
... print "in decorator init,", arg
... self.arg = arg
... def __call__(self, function):
... # this method is called to do the job
... print "in decorator call,", self.arg
... self.function = function
... return self._wrapper
... def _wrapper(self, *args, **kwargs):
... print "in the wrapper,", args, kwargs
... return self.function(*args, **kwargs)
>>> deco_instance = replacing_decorator_class('foo')
in decorator init, foo
>>> @deco_instance
... def function(*args, **kwargs):
... print "in function,", args, kwargs
in decorator call, foo
>>> function(11, 12)
in the wrapper, (11, 12) {}
in function, (11, 12) {}

像这样的装饰器可以做任何事,因为它能改变被装饰函数对象和参数,调用被装饰函数或不调用,最后改变返回值。

复制原始函数的文档字符串和其它属性

当新函数被返回代替装饰前的函数时,不幸的是原函数的函数名,文档字符串和参数列表都丢失了。这些属性可以部分通过设置__doc__(文档字符串),__module____name__(函数的全称)、__annotations__(Python 3中关于参数和返回值的额外信息)移植到新函数上,这些工作可通过functools.update_wrapper自动完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> import functools
>>> def better_replacing_decorator_with_args(arg):
... print "defining the decorator"
... def _decorator(function):
... print "doing decoration,", arg
... def _wrapper(*args, **kwargs):
... print "inside wrapper,", args, kwargs
... return function(*args, **kwargs)
... return functools.update_wrapper(_wrapper, function)
... return _decorator
>>> @better_replacing_decorator_with_args("abc")
... def function():
... "extensive documentation"
... print "inside function"
... return 14
defining the decorator
doing decoration, abc
>>> function
<function function at 0x...>
>>> print function.__doc__
extensive documentation

一件重要的东西是从可迁移属性列表中所缺少的:参数列表。参数的默认值可以通过__defaults____kwdefaults__属性更改,但是不幸的是参数列表本身不能被设置为属性。这意味着help(function)将显式无用的参数列表,使使用者迷惑不已。一个解决此问题有效但是丑陋的方式是使用eval动态创建wrapper。可以使用外部external模块自动实现。它提供了对decorator装饰器的支持,该装饰器接受wrapper并将之转换成保留函数签名的装饰器。

综上,装饰器应该总是使用functools.update_wrapper或者其它方式赋值函数属性。

标准库中的示例

首先要提及的是标准库中有一些实用的装饰器,有三种装饰器:

  • classmethod让一个方法变成“类方法”,即它能够无需创建实例调用。当一个常规方法被调用时,解释器插入实例对象作为第一个参数self。当类方法被调用时,类本身被给做第一个参数,一般叫cls

    类方法也能通过类命名空间读取,所以它们不必污染模块命名空间。类方法可用来提供替代的构建器(constructor):

    1
    2
    3
    4
    5
    6
    7
    8
    class Array(object):
    def __init__(self, data):
    self.data = data

    @classmethod
    def fromfile(cls, file):
    data = numpy.load(file)
    return cls(data)

这比用一大堆标记的__init__简单多了。

  • staticmethod应用到方法上让它们“静态”,例如,本来一个常规函数,但通过类命名空间存取。这在函数仅在类中需要时有用(它的名字应该以_为前缀),或者当我们想要用户以为方法连接到类时也有用——虽然对实现本身不必要。

  • property是对getter和setter问题Python风格的答案。通过property装饰的方法变成在属性存取时自动调用的getter。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> class A(object):
    ... @property
    ... def a(self):
    ... "an important attribute"
    ... return "a value"
    >>> A.a
    <property object at 0x...>
    >>> A().a
    'a value'

    例如A.a是只读属性,它已经有文档了:help(A)包含从getter方法获取的属性a的文档字符串。将a定义为property使它能够直接被计算,并且产生只读的副作用,因为没有定义任何setter。

    为了得到setter和getter,显然需要两个方法。从Python 2.6开始首选以下语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Rectangle(object):
    def __init__(self, edge):
    self.edge = edge

    @property
    def area(self):
    """Computed area.

    Setting this updates the edge length to the proper value.
    """

    return self.edge**2

    @area.setter
    def area(self, area):
    self.edge = area ** 0.5
通过`property`装饰器取代带一个属性(property)对象的getter方法,以上代码起作用。这个对象反过来有三个可用于装饰器的方法`getter`、`setter`和`deleter`。它们的作用就是设定属性对象的getter、setter和deleter(被存储为`fget`、`fset`和`fdel`属性(attributes))。当创建对象时,getter可以像上例一样设定。当定义setter时,我们已经在`area`中有property对象,可以通过`setter`方法向它添加setter,一切都在创建类时完成。

之后,当类实例创建后,property对象和特殊。当解释器执行属性存取、赋值或删除时,其执行被下放给property对象的方法。

为了让一切一清二楚[^5],让我们定义一个“调试”例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
>>> class D(object):
... @property
... def a(self):
... print "getting", 1
... return 1
... @a.setter
... def a(self, value):
... print "setting", value
... @a.deleter
... def a(self):
... print "deleting"
>>> D.a
<property object at 0x...>
>>> D.a.fget
<function a at 0x...>
>>> D.a.fset
<function a at 0x...>
>>> D.a.fdel
<function a at 0x...>
>>> d = D() # ... varies, this is not the same `a` function
>>> d.a
getting 1
1
>>> d.a = 2
setting 2
>>> del d.a
deleting
>>> d.a
getting 1
1
属性(property)是对装饰器语法的一点扩展。使用装饰器的一大前提——命名不重复——被违反了,但是目前没什么更好的发明。为getter,setter和deleter方法使用相同的名字还是个好的风格。

一些其它更新的例子包括:

  • functools.lru_cache记忆任意维持有限 参数:结果 对的缓存函数(Python 3.2)
  • functools.total_ordering是一个基于单个比较方法而填充丢失的比较(ordering)方法(__lt__,__gt____le__等等)的类装饰器。

函数的废弃

比如说我们想在第一次调用我们不希望被调用的函数时在标准错误打印一个废弃函数警告。如果我们不像更改函数,我们可用装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class deprecated(object):
"""Print a deprecation warning once on first use of the function.

>>> @deprecated() # doctest: +SKIP
... def f():
... pass
>>> f() # doctest: +SKIP
f is deprecated
"""

def __call__(self, func):
self.func = func
self.count = 0
return self._wrapper
def _wrapper(self, *args, **kwargs):
self.count += 1
if self.count == 1:
print self.func.__name__, 'is deprecated'
return self.func(*args, **kwargs)

也可以实现成函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def deprecated(func):
"""Print a deprecation warning once on first use of the function.

>>> @deprecated # doctest: +SKIP
... def f():
... pass
>>> f() # doctest: +SKIP
f is deprecated
"""

count = [0]
def wrapper(*args, **kwargs):
count[0] += 1
if count[0] == 1:
print func.__name__, 'is deprecated'
return func(*args, **kwargs)
return wrapper

while-loop移除装饰器

例如我们有个返回列表的函数,这个列表由循环创建。如果我们不知道需要多少对象,实现这个的标准方法如下:

1
2
3
4
5
6
7
8
def find_answers():
answers = []
while True:
ans = look_for_next_answer()
if ans is None:
break
answers.append(ans)
return answers

只要循环体很紧凑,这很好。一旦事情变得更复杂,正如真实的代码中发生的那样,这就很难读懂了。我们可以通过yield语句简化它,但之后用户不得不显式调用嗯list(find_answers())

我们可以创建一个为我们构建列表的装饰器:

1
2
3
4
def vectorized(generator_func):
def wrapper(*args, **kwargs):
return list(generator_func(*args, **kwargs))
return functools.update_wrapper(wrapper, generator_func)

然后函数变成这样:

1
2
3
4
5
6
7
@vectorized
def find_answers():
while True:
ans = look_for_next_answer()
if ans is None:
break
yield ans

插件注册系统

这是一个仅仅把它放进全局注册表中而不更改类的类装饰器,它属于返回被装饰对象的装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class WordProcessor(object):
PLUGINS = []
def process(self, text):
for plugin in self.PLUGINS:
text = plugin().cleanup(text)
return text

@classmethod
def plugin(cls, plugin):
cls.PLUGINS.append(plugin)

@WordProcessor.plugin
class CleanMdashesExtension(object):
def cleanup(self, text):
return text.replace('&mdash;', u'\N{em dash}')

这里我们使用装饰器完成插件注册。我们通过一个名词调用装饰器而不是一个动词,因为我们用它来声明我们的类是WordProcessor的一个插件。plugin方法仅仅将类添加进插件列表。

关于插件自身说下:它用真正的Unicode中的破折号符号替代HTML中的破折号。它利用unicode literal notation通过它在unicode数据库中的名称(“EM DASH”)插入一个符号。如果直接插入Unicode符号,将不可能区分所插入的和源程序中的破折号。

更多例子和参考

上下文管理器

上下文管理器是可以在with语句中使用,拥有__enter____exit__方法的对象。

1
2
with manager as var:
do_something(var)

相当于以下情况的简化:

1
2
3
4
5
var = manager.__enter__()
try:
do_something(var)
finally:
manager.__exit__()

换言之,PEP 343中定义的上下文管理器协议允许将无聊的try...except...finally结构抽象到一个单独的类中,仅仅留下关注的do_something部分。

1.__enter__方法首先被调用。它可以返回赋给var的值。as部分是可选的:如果它不出现,enter的返回值简单地被忽略。
2.with语句下的代码被执行。就像try子句,它们或者成功执行到底,或者breakcontinuereturn,或者可以抛出异常。无论哪种情况,该块结束后,__exit__方法被调用。如果抛出异常,异常信息被传递给__exit__,这将在下一章节讨论。通常情况下,异常可被忽略,就像在finally子句中一样,并且将在__exit__结束后重新抛出。

比如说我们想确认一个文件在完成写操作之后被立即关闭:

1
2
3
4
5
6
7
8
9
>>> class closing(object):
... def __init__(self, obj):
... self.obj = obj
... def __enter__(self):
... return self.obj
... def __exit__(self, *args):
... self.obj.close()
>>> with closing(open('/tmp/file', 'w')) as f:
... f.write('the contents\n')

这里我们确保了当with块退出时调用了f.close()。因为关闭文件是非常常见的操作,该支持已经出现在file类之中。它有一个__exit__方法调用close,并且本身可作为上下文管理器。

1
2
>>> with open('/tmp/file', 'a') as f:
... f.write('more contents\n')

try...finally常见的用法是释放资源。各种不同的情况实现相似:在__enter__阶段资源被获得,在__exit__阶段释放,如果抛出异常也被传递。正如文件操作,往往这是对象使用后的自然操作,内置支持使之很方便。每一个版本,Python都在更多的地方提供支持。

  • 所有类似文件的对象:
    • file ➔ 自动关闭
    • fileinput,tempfile(py >= 3.2)
    • bz2.BZ2Filegzip.GzipFile, tarfile.TarFile,zipfile.ZipFile
    • ftplib, nntplib ➔ 关闭连接(py >= 3.2)
    • multiprocessing.RLock ➔ 锁定和解锁
    • multiprocessing.Semaphore
    • memoryview ➔ 自动释放(py >= 3.2 或 3.3)
  • decimal.localcontext➔ 暂时更改计算精度
  • _winreg.PyHKEY ➔ 打开和关闭Hive Key
  • warnings.catch_warnings ➔ 暂时杀死(kill)警告
  • contextlib.closing ➔ 如上例,调用close
  • 并行编程
    • concurrent.futures.ThreadPoolExecutor ➔ 并行调用然后杀掉线程池(py >= 3.2)
    • concurrent.futures.ProcessPoolExecutor ➔ 并行调用并杀死进程池(py >= 3.2)
    • nogil ➔ 暂时解决GIL问题(仅仅cyphon :()

捕获异常

当一个异常在with块中抛出时,它作为参数传递给__exit__。三个参数被使用,和sys.exc_info()返回的相同:类型、值和回溯(traceback)。当没有异常抛出时,三个参数都是None。上下文管理器可以通过从__exit__返回一个真(True)值来“吞下”异常。例外可以轻易忽略,因为如果__exit__不使用return直接结束,返回None——一个假(False)值,之后在__exit__结束后重新抛出。

捕获异常的能力创造了有意思的可能性。一个来自单元测试的经典例子——我们想确保一些代码抛出正确种类的异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class assert_raises(object):
# based on pytest and unittest.TestCase
def __init__(self, type):
self.type = type
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
if type is None:
raise AssertionError('exception expected')
if issubclass(type, self.type):
return True # swallow the expected exception
raise AssertionError('wrong exception type')

with assert_raises(KeyError):
{}['foo']

使用生成器定义上下文管理器

当讨论生成器时,据说我们相比实现为类的迭代器更倾向于生成器,因为它们更短小方便,状态被局部保存而非实例和变量中。另一方面,正如双向通信章节描述的那样,生成器和它的调用者之间的数据流可以是双向的。包括异常,可以直接传递给生成器。我们想将上下文管理器实现为特殊的生成器函数。事实上,生成器协议被设计成支持这个用例。

1
2
3
4
5
6
7
@contextlib.contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>

contextlib.contextmanager装饰一个生成器并转换为上下文管理器。生成器必须遵循一些被包装(wrapper)函数强制执行的法则——最重要的是它至少yield一次。yield之前的部分从__enter__执行,上下文管理器中的代码块当生成器停在yield时执行,剩下的在__exit__中执行。如果异常被抛出,解释器通过__exit__的参数将之传递给包装函数,包装函数于是在yield语句处抛出异常。通过使用生成器,上下文管理器变得更短小精炼。

让我们用生成器重写closing的例子:

1
2
3
4
5
6
@contextlib.contextmanager
def closing(obj):
try:
yield obj
finally:
obj.close()

再把assert_raises改写成生成器:

1
2
3
4
5
6
7
8
9
10
@contextlib.contextmanager
def assert_raises(type):
try:
yield
except type:
return
except Exception as value:
raise AssertionError('wrong exception type')
else:
raise AssertionError('exception expected')

这里我们用装饰器将生成函数转化为上下文管理器!


FootNotes

[^2]:好吧它已经发布了= =,虽然在大多linux发行版中还是2.x和3.2。

[^5]:云里雾里= =

快速ebuild向导

这页是个非常简洁的ebuild写作指南。它不包含许多开发者面临的细节和问题,而是给出足够用来理解ebuild如何工作的微小的例子。

为了正确的涵盖所有来龙去脉,参见Ebuild WritingGeneral Concepts章节也很有用。

注意这里的例子,虽然基于真实的ebuilds树,但有些部分大刀阔斧的修剪、更改和简化了。

第一个Ebuild

我们从Exuberant Ctags工具开始,一个代码索引工具。这时一个简化的dev-util/ctags/ctags-5.5.4.ebuild(你能在主目录下找到真实的ebuild)

# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

EAPI=4

DESCRIPTION="Exuberant ctags generates tags files for quick source navigation"
HOMEPAGE="http://ctags.sourceforge.net"
SRC_URI="mirror://sourceforge/ctags/${P}.tar.gz"

LICENSE="GPL-2"
SLOT="0"
KEYWORDS="~mips ~sparc ~x86"

src_configure() {
    econf --with-posix-regex
}

src_install() {
    emake DESTDIR="${D}" install

    dodoc FAQ NEWS README
    dohtml EXTENDING.html ctags.html
}

基本格式

如你所见,ebuilds仅仅是在特殊环境中执行的bash脚本。

在ebuild的顶部是头块(header block),出现在所有ebuild中。

Ebuild使用tabs缩进,每个tab代表四格空格。参见Ebuild File Format.

信息变量

接着,有一系列变量将告诉Portage有关包和ebuild的各种东西。

ebuild的EAPI,参见EAPI Usage and Description

DESCRIPTION变量是包及包的作用的简短描述。

HOMEPAGE是链接到包的主页的链接。(切记包含http://部分)。

SRC_URI告诉Portage用来下载源码包的地址。这里,mirror://sourceforge/是意为“任何Sourceforge镜像”的特殊标记。${P}是由Portage设置的只读变量,即包名和版本——示例中是ctags-5.5.4

LICENSE是协议GPL-2(GNU General Public License version 2)。

SLOT告诉Portage这个包安装到哪个slot。

KEYWORDS变量设置ebuild测试的架构。我们使用keyword给我们新写的ebuild,包不允许被直接推送到稳定版,即使他们似乎工作。参见Keywording查看细节。

构建函数

接着一个叫src_configure的函数,Portage将在配置(configure)包时调用它。econf是执行./configure的一个封装。如果由于某些原因在econf时出错,Portage将停止而非继续安装。

当Portage准备安装包时会调用src_install函数。这里有点微妙——并非直接安装到文件系统,我们必须安装到一个Portage通过${D}变量给出的特殊位置(Portage设置这个——参见Install DestinationsSandbox)

注意:常规安装方法是emake DESTDIR="${D}" install,这适合所有符合标准的Makefile。如果给出sandbox错误,尝试用einstall代替。如果仍然失败,参看src_install如何手工安装。

dodocdohtml部分是安装文件到相应的/usr/share/doc部分的辅助函数。

ebuild可以定义其它函数(参见Ebuild Functions)。在大多情况下,Portage提供合理的默认实现,通常做正确的事情,不需要定义src_unpacksrc_compile函数。例如,src_unpack函数被用来解包或给源码打补丁,但是这个例子中默认实现做了我们所需要的所有事情。同样默认的src_compile函数将调用emake——一个make的封装。

注意:先前|| die结构不得不添加到每个命令后去检查错误。这在EAPI 4中不在必要——如果什么出错的话Portage提供的函数将自己die。

含依赖的ebuild

在ctags的例子中,我们没告诉Portage有关任何依赖。当情况是这样时,没关系,因为ctags仅仅需要一个基本的工具链来编译和运行(参见Implicit System Dependency理解为何我们不需要显式依赖)。然而事情很少这么简单。

这是app-misc/detox/detox-1.1.1.ebuild

# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

EAPI=4

DESCRIPTION="detox safely removes spaces and strange characters from filenames"
HOMEPAGE="http://detox.sourceforge.net/"
SRC_URI="mirror://sourceforge/${PN}/${P}.tar.bz2"

LICENSE="BSD"
SLOT="0"
KEYWORDS="~hppa ~mips sparc x86"

RDEPEND="dev-libs/popt"
DEPEND="${RDEPEND}
    sys-devel/flex
    sys-devel/bison"

src_configure() {
    econf --with-popt
}

src_install() {
    emake DESTDIR="${D}" install
    dodoc README CHANGES
}

你再次看到ebuild头和不通的信息变量。在SRC_URI中,${PN}用来获取不含尾部版本的包名(还有更多的这种变量——参见Predefined Read-Only Variables)。

我们再次定义了src_configuresrc_install函数。

Portage依靠DEPENDRDEPEND变量决定构建和运行包需要哪些变量。DEPEND变量列出编译时依赖,RDEPEND变量列出运行时依赖。参见Dependencies获取更复杂的例子。

带补丁的ebuild

我们经常要打补丁。这通过epatch辅助函数在src_prepare函数中完成。为了使用epatch,必须告诉Portage需要的eutilseclass(eclass就像库一样)——这通过在ebuild顶部的inherit eutils完成。这是app-misc/detoxdetox-1.1.0.ebuild

# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

EAPI=4

inherit eutils

DESCRIPTION="detox safely removes spaces and strange characters from filenames"
HOMEPAGE="http://detox.sourceforge.net/"
SRC_URI="mirror://sourceforge/${PN}/${P}.tar.bz2"

LICENSE="BSD"
SLOT="0"
KEYWORDS="~hppa ~mips ~sparc ~x86"

RDEPEND="dev-libs/popt"
DEPEND="${RDEPEND}
    sys-devel/flex
    sys-devel/bison"

src_prepare() {
    epatch "${FILESDIR}"/${P}-destdir.patch \
        "${FILESDIR}"/${P}-parallel_build.patch
}

src_configure() {
    econf --with-popt
}

src_install() {
    emake DESTDIR="${D}" install
    dodoc README CHANGES
}

注意${FILESDIR}/${P}-destdir.patch指向detox-1.1.0-destdir.patch,这个文件在Portage树的files/子文件夹中。更大的补丁文件必须在你的开发者空间dev.gentoo.org而不是files/或镜像中——参见Gentoo MirrorsPatching with epatch

带USE标记的ebuild

USE标记,这里有个例子dev-libs/libiconv/libiconv-1.9.2.ebuild,一个libc实现的iconv替代。

# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

EAPI=4

DESCRIPTION="GNU charset conversion library for libc which doesn't implement it"
HOMEPAGE="http://www.gnu.org/software/libiconv/"
SRC_URI="ftp://ftp.gnu.org/pub/gnu/libiconv/${P}.tar.gz"

LICENSE="LGPL-2.1"
SLOT="0"
KEYWORDS="~amd64 ~ppc ~sparc ~x86"
IUSE="nls"

DEPEND="!sys-libs/glibc"

src_configure() {
    econf $(use_enable nls)
}

src_install() {
    emake DESTDIR="${D}" install
}

注意IUSE变量。这列出了所有(非特殊)ebuild使用的use标记。除其它事项外,它还将被用作emerge -pv时输出。

这个包的./configure脚本使用了常规的--enable-nls--disable-nls参数。我们用use_enable工具函数依赖用户USE标记自动生成这个(参见Query Function Reference)。

另一个复杂的例子是mail-client/sylpheed/sylpheed-1.0.4.ebuild

# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

EAPI=4

inherit eutils

DESCRIPTION="A lightweight email client and newsreader"
HOMEPAGE="http://sylpheed.good-day.net/"
SRC_URI="mirror://sourceforge/${PN}/${P}.tar.bz2"

LICENSE="GPL-2"
SLOT="0"
KEYWORDS="alpha amd64 hppa ia64 ppc ppc64 sparc x86"
IUSE="crypt imlib ipv6 ldap nls pda ssl xface"

RDEPEND="=x11-libs/gtk+-2*
    crypt? ( >=app-crypt/gpgme-0.4.5 )
    imlib? ( media-libs/imlib2 )
    ldap? ( >=net-nds/openldap-2.0.11 )
    pda? ( app-pda/jpilot )
    ssl? ( dev-libs/openssl )
    xface? ( >=media-libs/compface-1.4 )
    app-misc/mime-types
    x11-misc/shared-mime-info"
DEPEND="${RDEPEND}
    dev-util/pkgconfig
    nls? ( >=sys-devel/gettext-0.12.1 )"

src_prepare() {
    epatch "${FILESDIR}"/${PN}-namespace.diff \
        "${FILESDIR}"/${PN}-procmime.diff
}

src_configure() {
    econf \
        $(use_enable nls) \
        $(use_enable ssl) \
        $(use_enable crypt gpgme) \
        $(use_enable pda jpilot) \
        $(use_enable ldap) \
        $(use_enable ipv6) \
        $(use_enable imlib) \
        $(use_enable xface compface)
}

src_install() {
    emake DESTDIR="${D}" install

    doicon sylpheed.png
    domenu sylpheed.desktop

    dodoc [A-Z][A-Z]* ChangeLog*
}

注意可选依赖。有些use_enable行使用两个参数的版本——这在USE标记名不完全匹配./configure参数时非常有用。

Image to CSS

Warning: 我警告过了,浏览器弱爆了的就不要点开了= =。内存太小了就慎重打开= =

眼残,竟然看到这种奇葩的演示

看了看CSS源码,想起来可以用Python Image Library做一个generator。

结果就真得做了一个……

Codes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#! /bin/env python
# -*- coding: utf-8 -*-

"""
Script to turn image into css
"""


import Image
import sys

__author__ = "Reverland (lhtlyy@gmail.com)"


def getcss(im):
"""docstring for get"""
css = """position: absolute;
top: 30px;
left: 30px;
width: 0;
height: 0;
box-shadow:
"""

string = '%dpx %dpx 0px 1px rgb%s,\n'
for y in range(0, im.size[1], 1):
for x in range(0, im.size[0], 1):
if im.size[1] - y <= 1 and im.size[0] - x <= 1:
string = '%dpx %dpx 0px 1px rgb%s;\n'
color = im.getpixel((x, y))
css += string % (x, y, color)
return css


def gethtml(css):
"""docstring for gethtml"""
html = """
<div style="
%s"></div>
""" % css

return html

if __name__ == '__main__':
filename = sys.argv[1]
#outfile = sys.argv[2]
im = Image.open(filename)
ratio = 0.5
size = (int(ratio * im.size[0]), int(ratio * im.size[1]))
im.thumbnail(size)
html = gethtml(getcss(im))
print html
# with open(outfile, 'wb') as f:
# f.write(html)

Demo

点击显示图像

意义?

意义在于可能你会死机= =

尝试复制看看?

Python小练习:追踪导弹仿真

警告:浏览器可能不支持html嵌入标签,那么很抱歉什么视频也看不到。建议使用最新稳定版的firefox/chromium

仿真的时候面向对象会很方便。

缘起

某天终于没有雾霾的时候,我在操场上追赶正在散步的父亲……然后,我就想多了= =跟踪导弹是个什么轨迹呢?

操场示意图

仿真

首先把问题简化下,如果从圆心开始追赶圆上匀速运动的物体,是什么情况。

我先自己设法用微分笔算了算,发现实在搞不定。

上网查查导弹问题看到一些简单的直线问题,都涉及一堆微分方程和欧拉法迭代啥的……

干脆自己仿真下吧。这是最初的版本,完全没有面向对象概念。

前半部分调整图像的代码完全可以不看,从while循环开始即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import matplotlib.pyplot as plt
import numpy as np
tolerance = 1e-1
radius = np.pi
v_o = 20
x_o, y_o = 0, radius

x_m, y_m = -radius, 0
v_m = 5

plt.figure(figsize=(10, 10), dpi=80)
plt.title(" missile flight simulator ", fontsize=40)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
#plt.xticks([])
#plt.yticks([])

# set spines
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data', 0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data', 0))
plt.xticks([-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

# Note object and missile
plt.annotate('object start point', xy=(x_o, y_o), xycoords='data',
xytext=(+15, +15), textcoords='offset points', fontsize=12,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate('missile start point', xy=(x_m, y_m), xycoords='data',
xytext=(+15, +15), textcoords='offset points', fontsize=12,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

# alpha labels
for label in ax.get_xticklabels() + ax.get_yticklabels():
label.set_fontsize(16)
label.set_bbox(dict(facecolor='white', edgecolor='None', alpha=0.65))


while True:
if x_o == 0 and y_o == radius:
beta = 0
elif x_o == 0 and y_o == radius:
beta = np.pi
elif x_o < 0:
beta = np.pi / 2 * 3 - np.arctan(y_o / x_o)
else:
beta = np.pi / 2 - np.arctan(y_o / x_o)
if np.sqrt((x_o - x_m) ** 2 + (y_o - y_m) ** 2) < tolerance:
print "collision"
plt.plot(x_m, y_m, 'o')
plt.annotate('crash point', xy=(x_m, y_m), xycoords='data',
xytext=(+15, +15), textcoords='offset points', fontsize=12,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.pause(0.1)
break
elif x_o < x_m:
alpha = np.pi + np.arctan((y_o - y_m) / (x_o - x_m))
elif x_o == x_m:
alpha = np.pi / 2
else:
alpha = np.arctan((y_o - y_m) / (x_o - x_m))
x_o = radius * np.sin(beta + v_o * 0.01 / np.pi / 2)
y_o = radius * np.cos(beta + v_o * 0.01 / np.pi / 2)
x_m = x_m + v_m * 0.01 * np.cos(alpha)
y_m = y_m + v_m * 0.01 * np.sin(alpha)
#print alpha, beta
plt.plot(x_o, y_o, 'r.', alpha=.5)
plt.plot(x_m, y_m, 'bx', alpha=.5)
plt.legend(("target", "missile"), loc="upper left", prop={'size': 12})
plt.pause(0.1)

我发现如果我速度够慢,未必追得上,甚至连被追踪物的轨道都不会进入……这挺出乎意料的,本来以为一定追的上

回到最初的问题,我从我的位置上追我父亲(好傻,都不知道估计下位置……)

面向对象

问题来了,如果我要仿真不只一个追踪导弹,比如还想仿真一个拦截导弹呢?

拦截失败演示

还可以用上面的方法不断扩充代码,每个对象写重复的代码。

但这时面向对象就能发挥威力,减少代码重用了。

以下是对四蜗牛聚合线问题的仿真。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import matplotlib.pyplot as plt
import numpy as np
tolerance = 1e-1
radius = np.pi

# missile 1
x_m1, y_m1 = -np.pi, 0
v_m1 = 5

# missile 2
x_m2, y_m2 = 0, np.pi
v_m2 = v_m1
# missile 3
x_m3, y_m3 = np.pi, 0
v_m3 = v_m1
# missile 4
x_m4, y_m4 = 0, -np.pi
v_m4 = v_m1

plt.figure(figsize=(10, 10), dpi=80)
plt.title(" missile flight simulator ", fontsize=40)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
#plt.xticks([])
#plt.yticks([])

# set spines
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data', 0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data', 0))
plt.xticks([-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
plt.yticks([-np.pi, -np.pi / 2, 0, np.pi / 2, np.pi], [r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])

plt.annotate('missile start point', xy=(x_m1, y_m1), xycoords='data',
xytext=(+15, +15), textcoords='offset points', fontsize=12,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))

# alpha labels
for label in ax.get_xticklabels() + ax.get_yticklabels():
label.set_fontsize(16)
label.set_bbox(dict(facecolor='white', edgecolor='None', alpha=0.65))


class ob(object):
"""docstring for ob"""
def __init__(self, x, y):
self.x = x
self.y = y


class missile(ob):
"""docstring for missile"""
def __init__(self, x, y):
super(missile, self).__init__(x, y)

def forward(self, v, target):
"""docstring for forward"""
if self.x < target.x:
alpha = np.arctan((target.y - self.y) / (target.x - self.x))
elif self.x > target.x:
alpha = np.pi + np.arctan((target.y - self.y) / (target.x - self.x))
elif self.x == target.x and self.y < target.y:
alpha = np.pi / 2
else:
alpha = -np.pi / 2
self.x = self.x + v * 0.01 * np.cos(alpha)
self.y = self.y + v * 0.01 * np.sin(alpha)
return self.x, self.y

def distance(self, target):
"""docstring for distance"""
return np.sqrt((self.x - target.x) ** 2 + (self.y - target.y) ** 2)


class target(ob):
"""docstring for target"""
def __init__(self, x, y):
super(target, self).__init__(x, y)

def newposition(self, x, y):
"""docstring for newposition"""
self.x = x
self.y = y

m1 = missile(x_m1, y_m1)
m2 = missile(x_m2, y_m2)
m3 = missile(x_m3, y_m3)
m4 = missile(x_m4, y_m4)

while True:
if m1.distance(m2) < tolerance or m1.distance(m3) < tolerance or m1.distance(m4) < tolerance:
print "collision"
plt.plot(x_m1, y_m1, 'o')
plt.annotate('crash point', xy=(x_m1, y_m1), xycoords='data',
xytext=(+15, +15), textcoords='offset points', fontsize=12,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.pause(0.1)
plt.show()
break
elif m3.distance(m2) < tolerance or m3.distance(m4) < tolerance:
print "collision"
plt.plot(x_m3, y_m3, 'o')
plt.annotate('crash point', xy=(x_m3, y_m3), xycoords='data',
xytext=(+15, +15), textcoords='offset points', fontsize=12,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.pause(0.1)
plt.show
break
x_m1, y_m1 = m1.forward(v_m1, m2)
x_m2, y_m2 = m2.forward(v_m2, m3)
x_m3, y_m3 = m3.forward(v_m3, m4)
x_m4, y_m4 = m4.forward(v_m4, m1)
#print alpha, beta
plt.plot(x_m1, y_m1, 'bx', alpha=.5)
plt.plot(x_m2, y_m2, 'k*', alpha=.5)
plt.plot(x_m3, y_m3, 'r.', alpha=.5)
plt.plot(x_m4, y_m4, 'gp', alpha=.5)
plt.legend(("missile1", "missile2", "missile3", "missile4"), loc="upper left", prop={'size': 12})
plt.pause(0.1)

总结

面向对象方法对仿真问题非常合适,能有效简化代码,做到DRY(Don’t repeat yourself)。

搞着玩的,也许我该想想复试怎么办了……

Generate Ascii Image Like the Matrix

Inspired by this online image to text converter and this post on Oschina

script hosts at github. Poor hack, Any advice is welcomed.

Edit: Oooops, Demo sucks on my site, it’s perfect if you generate an alone html file.look here and here

Demo





















































源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#! /bin/env python
# -*- coding: utf-8 -*-

"""
Turn images into acsii.
"""


__author__ = 'Reverland (lhtlyy@gmail.com)'

import Image
import ImageOps
import sys


filename = 'a.jpg'


def makeHTMLbox(body, fontsize, imagesize):
"""takes one long string of words and a width(px) then put them in an HTML box"""
boxStr = """<div style=\"font-size: %spx;line-height: 100%s; width: %s;background-color: rgb(0, 0, 0);border: 1px grey solid;text-align: center; overflow: hidden;\">%s</div>
"""

return boxStr % (fontsize, '%', imagesize[0], body)


def makeHTMLascii(body, color):
"""take words and , and create an HTML word """
#num = str(random.randint(0,255))
# return random color for every tags
color = 'rgb(%s, %s, %s)' % color
# get the html data
wordStr = '<span style=\"color:%s;float:left;\">%s</span>'
return wordStr % (color, body)


def i2m(im, fontsize):
"""turn an image into ascii like matrix"""
im = im.convert('L')
im = ImageOps.autocontrast(im)
im.thumbnail((im.size[0] / fontsize, im.size[1] / fontsize))
string = ''
colors = [(0, i, 0) for i in range(0, 256, 17)]
words = '据说只有到了十五字才会有经验的'
for y in range(im.size[1]):
for x in range(im.size[0]):
p = im.getpixel((x, y))
i = 14
while i >= 0:
if p >= i * 17:
s = makeHTMLascii(words[3 * i:3 * (i + 1)], colors[i])
break
i -= 1
if x % im.size[0] == 0 and y > 0:
s = s + '<br/>'
string = string + s
return string


def i2a(im, fontsize):
"""turn an image into ascii with colors"""
im = im.convert('RGB')
im = ImageOps.autocontrast(im)
im.thumbnail((im.size[0] / fontsize, im.size[1] / fontsize))
string = ''
for y in range(im.size[1]):
for x in range(im.size[0]):
c = im.getpixel((x, y))
# print c
s = makeHTMLascii('翻', c)
if x % im.size[0] == 0 and y > 0:
s = s + '<br/>'
string = string + s
return string


def getHTMLascii(filename, fontsize, style='matrix', outputfile='a.html', scale=1):
"""Got html ascii image"""
im = Image.open(filename)
size = (int(im.size[0] * scale), int(im.size[1] * scale))
im.thumbnail(size, Image.ANTIALIAS)
if style == 'matrix':
ascii = makeHTMLbox(i2m(im, fontsize), fontsize, im.size)
elif style == 'ascii':
ascii = i2a(im, fontsize)
else:
print "Just support ascii and matrix now, fall back to matrix"
ascii = makeHTMLbox(i2m(im, fontsize), fontsize, im.size)
with open(outputfile, 'wb') as f:
f.write(ascii)
return 1


if __name__ == '__main__':
if sys.argv[1] == '--help' or sys.argv[1] == '-h':
print """Usage:python i2a.py filename fontsize [optional-parameter]
optional-parameter:
scale -- between (0, 1)
style -- matrix or ascii"""

else:
filename = sys.argv[1]
try:
fontsize = int(sys.argv[2])
except:
fontsize = int(raw_input('input fontsize please:'))
try:
scale = float(sys.argv[3])
except:
scale = 1
try:
style = sys.argv[4]
except:
style = 'matrix'
getHTMLascii(filename, fontsize, scale=scale)

More Demo




























































Yet Another PhotoMosaic Generator

Update: Mon 25 Feb 2013 11:38:47 AM CST add classic style. More refer to github

Python是面向对象的?没有对象面向毛对象。

——Anonymous

Several weeks ago, I saw a poster of presidential campaign for Obama, in which Obama’s portrait was made up of many voter’s photos. It really attracted me, somedays later, I want to make one myself.

The completed code host here. It is much more functional than object-oriented…

Search the Internet

First of all, I searched the Google to find out how others achieve it, then I found many interesting implement and post on it.Along with them, there are pretty demos around.One of the demo of Foto-Mosaik-Edda striked me.It declaims as follows in their site:

The Chaos Mosaic Picture is a new form of photo mosaic which can, at present, only be created by Foto-Mosaik-Edda.

Chaos Mosaic Picture

Uhm…Foto-Mosaic-Edda is an open-source project that really impressive.But it was an C# project. Linux users don’t like it however.I don’t like mono.

I searched other open-source implement on photomosaic. I get some simple programs only use gray photos, and some complex ones can make beautiful classic photomosaic(like metapixel, even chaos style which it calls collage style), But none has as beautiful demos as Foto-Mosaic-Edda.(metapixel really amazing, it is robust and quickly.)

However, I saw many enthusiastic people write one themselves, it really looks interesting for me. I’ve used PIL for processing images when I tried to decode captchas several days ago, so I believe with the help of PIL, someone can achieve photomosaic simply.

So I just read the documentation of PIL, then start my hack.

Write My Own PhotoMosaic Generator

It’s not hard, however, what you should do is clear and simple:

  • analyse the image to be made mosaic, get a dict in which position as key and color as value.
  • use a bunch of images to get a dict, in which image name as key and colors as value.
  • thumbnail bunches of images and paste it in the right position, so that the big image looks like it consists of many small one.

I’d like to got the chaos style, so some other requirements:

  • frame and shadow for small images
  • random paste small images onto large one

Now, let’s go.

Frame, Shadow and Rotate

first add frame, shadow to small images

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def add_frame(image):
'''Add frame for image.'''
im = ImageOps.expand(image, border=int(0.01 * max(image.size)), fill=0xffffff)
return im


def drop_shadow(image, offset, border=0, shadow_color=0x444444):
"""Add shadows for image"""
# Caclulate size
fullWidth = image.size[0] + abs(offset[0]) + 2 * border
fullHeight = image.size[1] + abs(offset[1]) + 2 * border
# Create shadow, hardcode color
shadow = Image.new('RGBA', (fullWidth, fullHeight), (0, 0, 0))
# Place the shadow, with required offset
shadowLeft = border + max(offset[0], 0) # if <0, push the rest of the image right
shadowTop = border + max(offset[1], 0) # if <0, push the rest of the image down
shadow.paste(shadow_color, [shadowLeft, shadowTop, shadowLeft + image.size[0], shadowTop + image.size[1]])
shadow_mask = shadow.convert("L")
# Paste the original image on top of the shadow
imgLeft = border - min(offset[0], 0) # if the shadow offset was <0, push right
imgTop = border - min(offset[1], 0) # if the shadow offset was <0, push down
shadow.putalpha(shadow_mask)
shadow.paste(image, (imgLeft, imgTop))
return shadow

Then a function to rotate images.

1
2
3
4
5
6
def rotate_image(image, degree):
'''Rotate images for specific degree. Expand to show all'''
if image.mode != 'RGBA':
image = image.convert('RGBA')
im = image.rotate(degree, expand=1)
return im

‘RGBA’ mode is to support transparency. What’s matter here is that jpeg/jpg does not support transparency. So you can’t get transparency shadows and rotate pictures if you just use jpg/jpeg images.So, write a function to process images with jpg/jpeg format, transpose it into png.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def process_image(filename, newname):
'''convert image to png to support transparency'''
if filename.split('.')[-1] != 'png':
im = Image.open(filename)
im.save(newname + '.png')
print "processing image file %s" % filename
return 1


def process_directory(path):
os.chdir(path)
count = 1
for filename in os.listdir(path):
ext = filename.split('.')[-1]
if ext == 'jpeg' or ext == 'jpg':
process_image(filename, str(count))
os.remove(filename)
count += 1
return 1

Really poor work… But it works for me: )

We have to thumnail bunches of images, It’s easy to thumbnail with PIL:

1
2
3
4
def thumbnail(im, size):
"""thumnail the image"""
im.thumbnail(size, Image.ANTIALIAS)
return im

Let’s have a fun with them. To get heaps of images randomly on the desktop, I hardcoded these parameters to get my photos work, you HAVE TO find yours:

1
2
3
4
5
6
7
8
9
10
# Just for fun
def chao_image(path, size=(800, 800), thumbnail_size=(50, 50), shadow_offset=(10, 10), backgroud_color=0xffffff):
image_all = Image.new('RGB', size, backgroud_color)
for image in os.listdir(path):
if image.split('.')[-1] == 'png':
im = Image.open(image)
degree = random.randint(-30, 30)
im = thumbnail(rotate_image(drop_shadow(add_frame(im), shadow_offset), degree), thumbnail_size)
image_all.paste(im, (random.randint(-thumbnail_size[0], size[0]), random.randint(-thumbnail_size[1], size[1])), im)
return image_all

Calculate Images And Compare

Get average colors of an image

1
2
3
4
def average_image(im):
"""return average (r,g,b) for image"""
color_vector = [int(x) for x in ImageStat.Stat(im).mean]
return color_vector

to compare images? Compare the (r,g,b) value of them.

1
2
3
4
5
6
7
8
9
def compare_vectors(v1, v2):
"""compare image1 and image2, return relations"""
if len(v1) == len(v2):
distance = 0
for i in xrange(len(v1)):
distance += (v1[i] - v2[i]) ** 2
return distance
else:
print "vector not match in dimensions"

I just use distance in (R, G, B) space to calculate similarity, someone advice compare in other space, you can change it just like the example in PIL’s documentation:

1
2
3
4
5
6
# May not useful
def rgb2xyz(im):
"""rgb to xyz"""
rgb2xyz = (0.412453, 0.357580, 0.180423, 0, 0.212671, 0.715160, 0.072169, 0, 0.019334, 0.119193, 0.950227, 0)
out = im.convert("RGB", rgb2xyz)
return out

But I find many implements just use R,G,B, and it works well.

Next, get a dict of image in current path, in which filename as key, average (R,G,B) colors as value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def tile_dict(path):
"""Return list of average (R,G,B) for image in this path as dict."""
dic = {}
for image in os.listdir(path):
if image.split('.')[-1] == 'png':
try:
im = Image.open(image)
except:
print "image file %s cannot open" % image
continue
if im.mode != 'RGB':
im = im.convert('RGB')
dic[image] = average_image(im)
return dic

We don’t need to calculate every pixel of the large picture, just thumbnail it to get a nearest color of different regions.

1
2
3
4
5
6
def thumbnail_background(im, scale):
"""thumbnail backgroud image"""
newsize = im.size[0] / scale, im.size[1] / scale
im.thumbnail(newsize)
print 'thumbnail size and the number of tiles %d X %d' % im.size
return im.size

For every pixel in the thumbnailed large image, find most similar small image filenames.(top ten):

1
2
3
4
5
6
7
8
9
10
def find_similar(lst, dic):
"""for lst([R, G, B], Calculate which key-value in dic has the most similarity.Return first 10)"""
similar = {}
for k, v in dic.items():
similar[k] = compare_vectors(v, lst)
# if len(v) != len(lst):
# print v, len(v), lst, len(lst)
similar = [(v, k) for k, v in similar.items()] # Not good, lost the same Score
similar.sort()
return similar[:10]

Poor hack, but it really works…

Final Work

Now it’s the final magic.

Get the small image in order, the order imply where it should be. Then rotate, add shadows and frames for small images, finally paste it onto the large one randomly in the right position:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def get_image_list(im, dic):
"""receive a thumbnail image and a dict of image to be mosaic, return tiles(filename) in order(as a list)"""
lst = list(im.getdata())
tiles = []
for i in range(len(lst)):
#print find_similar(lst[i], dic)[random.randrange(10)][1]
tiles.append(find_similar(lst[i], dic)[random.randrange(10)][1])
return tiles


def paste_chaos(image, tiles, size, shadow_off_set=(30, 30)):
"""size is thumbnail of backgroud size that is how many tiles per line and row"""
# image_all = Image.new('RGB', image.size, 0xffffff)
image_all = image
lst = range(len(tiles))
random.shuffle(lst)
fragment_size = (image.size[0] / size[0], image.size[1] / size[1])
print 'tiles size %d X %d' % fragment_size
print 'number of tiles one iteration: %d' % len(lst)
for i in lst:
im = Image.open(tiles[i])
degree = random.randint(-20, 20)
im = thumbnail(rotate_image(drop_shadow(add_frame(im), shadow_off_set), degree), (fragment_size[0] * 3 / 2, fragment_size[1] * 3 / 2))
x = i % size[0] * fragment_size[0] + random.randrange(-fragment_size[0] / 2, fragment_size[0] / 2)
y = i / size[0] * fragment_size[1] + random.randrange(-fragment_size[1] / 2, fragment_size[1] / 2)
# print x, y
image_all.paste(im, (x, y), im)
return image_all

I try it like this, I know parameter n is tricky, it was the scale it thumbnail the large image. Maybe I’ll change it to something more clear later…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def main(filename, n, scale, iteration, path='./'):
# 0. select an big image for mosaic
print "open %s" % filename
im = Image.open(filename)
# 1. process image as png to support transparency
print "process directory %s" % path
process_directory(path)
# 2. get a dict for path
print "get tile dict for path `%s`" % path
try:
with open('dic.txt', 'r') as f:
dic = p.load(f)
except:
dic = tile_dict(path)
with open('dic.txt', 'wb') as f:
p.dump(dic, f)
# 3. thumbnail the big image for compare
print "thumbnail background for compare"
# n = 30 # 原始图片缩为多少分之一
# scale = 3 # 原始图片放大倍数
big_size = im.size[0] * scale, im.size[1] * scale
im_chao = Image.new('RGB', big_size, 0xffffff)
imb_t_size = thumbnail_background(im, n)
print "how may tiles: %d X %d" % imb_t_size
print 'number of iterations: %d' % iteration
for i in range(iteration):
print 'iteration: %d' % (i + 1)
# 4. get a list of smail image for mosaic
print "get pic list"
im_tiles = get_image_list(im, dic)
# 5. paste in chaos style
print "generate final image"
im_chao = paste_chaos(im_chao, im_tiles, imb_t_size)
return im_chao


if __name__ == '__main__':
im = main('../mm.jpg', 15, 5, 2)
im.save('../final3.png')
im.show()

Demo

Do you like it?

Demo

Can you see it?

Demo Download(45.8M)

More examples here(Chinese)

Thanks

My family, It supports me.Never let me down, never pour cold water, never scold for insignificance.

可视化人人好友关系


目录

  • toc
    {: toc}

R分析人人网好友推荐系统用python进行人人好友分析启发,完全用python的模块和方式实现了一遍,结果搞得好像一点也不Pythonic,倒好像有点继承了之前在lisp下养成的函数式风格……

作为菜鸟深知代码写得不怎么样,写在这里,希望没什么基础的人都能体会到其中我所感受到的乐趣Happy hacking,也欢迎各路高手大牛不吝赐教。

完整代码见github/reverland/scripts/renren.py

必要条件

For Reader:

读者需要有一定python基础,如果没有,不妨花半个小时看看Python简明教程

For Computer:

我在gentoo linux下完成所有的编写测试,也推荐想尝试的朋友选择linux环境。不过只是推荐,python作为著名的跨平台语言,其代码可以没什么差别的运行在各个平台上,但你需要以下一些必备的东西:

  • python 2.7 也许2.5也行,cookielib之前好像不在标准库中,而python3中则有改动。
  • networkx 一个分析,操作,绘制网络的python模块。
  • matplotlib 经常用来绘图的python模块

怎么安装请自行参照官方网站说明。对后两个模块,建议使用pip安装,这货就相当于个包管理器(一条命令完成搜索下载安装所有操作并自动处理所有依赖)。

最后,还有可选的开发环境:ipython,该程序提供一个功能强大的交互环境,很方便做测试调试探索各种 一次性 工作。

我们要做些什么

从人人网上抓取好友,绘制好友之间的关系图,还可以供进一步分析(貌似没什么好分析的)。

为了实现这点我们需要做到以下几个工作:

  • 模拟登录[^1]
  • 提取数据以合适数据结构保存
  • 制作图像并绘制

模拟登录

人人的模拟登录还是比较简单的。模拟登录最困难的部分就是对要登录网站登录过程的分析。通常办法是通过抓包,用wireshark总有种杀鸡用牛刀的感觉,而且当你像作者一样天天用socks代理时会发现什么也抓不到……所以IE/Chrome/Firefox的开发工具可能更合适。这里用firebug,你可以在火狐扩展中心找到并安装它。

人人登录分析

然后在抓包过程中找到用户名^2和登录时请求的服务器。

不过,之前有很多人已经分析过人人的登录过程(一般不会要求验证码,除非登录过于频繁)。你所必须要做的基本上只有两件事:

  • 将用户名和密码POST到服务器
  • 处理cookie

模拟登录的工具使用python的标准库中的urllib,urllib2cookielib即可

1
2
3
import urllib
import urllib2
import cookielib

如果对这三个标准库不熟悉,建议花时间看看下面两篇教程。不过也许无所谓,代码可以自己解释自己:p。

当浏览器使用POST方法请求服务器时,它将参数经过编码附加到url后传递过去:

http://www.renren.com/ajaxLogin/login&email=username&password=blablabla

登录成功后,还要获取人人中用来作为用户唯一标识额uid(打开人人主页注意url就看到了)并返回,以供将来使用。将来所有的抓取都通过独一无二的uid而非可能重名的姓名。

使用正则抓去uid

import re

我们先写登录函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def login(username, password):
"""log in and return uid"""
logpage = "http://www.renren.com/ajaxLogin/login"
data = {'email': username, 'password': password}
login_data = urllib.urlencode(data)
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
res = opener.open(logpage, login_data)
print "Login now ..."
html = res.read()
#print html

# Get uid
print "Getting user id of you now"
res = urllib2.urlopen("http://www.renren.com/home")
html = res.read()
# print html
uid = re.search("'ruid':'(\d+)'", html).group(1)
# print uid
print "Login and got uid successfully"
return uid

不妨在ipython中先测试下。

抓取数据

每个人的好友都可以从页面http://friend.renren.com/GetFriendList.do?curpage=0&id=uid获取,虽然人人已经改版,但这个页面还能用。其中curpage参数的值是页码,id参数的值是拟抓取对象的用户ID。通过循环抓取所有好友并以用户id为键姓名为值保存为字典。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def getfriends(uid):
"""Get the uid's friends and return the dict with uid as key,name as value."""
print "Get %s 's friend list" % str(uid)
pagenum = 0
dict1 = {}
while True:
targetpage = "http://friend.renren.com/GetFriendList.do?curpage=" + str(pagenum) + "&id=" + str(uid)
res = urllib2.urlopen(targetpage)
html = res.read()

pattern = '<a href="http://www\.renren\.com/profile\.do\?id=(\d+)"><img src="[\S]*" alt="[\S]*[\s]\((.*)\)" />'

m = re.findall(pattern, html)
#print len(m)
if len(m) == 0:
break
for i in range(0, len(m)):
no = m[i][0]
uname = m[i][1]
#print uname, no
dict1[no] = uname
pagenum += 1
print "Got %s 's friends list successfully." % str(uid)
return dict1

我们再写个获取好友关系字典的函数,为了避免我们每次为了获取字典都要登录抓取。

1
2
3
4
5
6
7
8
9
10
def getdict(uid):
"""cache dict of uid in the disk."""
try:
with open(str(uid) + '.txt', 'r') as f:
dict_uid = p.load(f)
except:
with open(str(uid) + '.txt', 'w') as f:
p.dump(getfriends(uid), f)
dict_uid = getdict(uid)
return dict_uid

我们还需要一个用来判断两个人关系的函数,来判断我们好友之间的关系。

1
2
3
4
5
6
7
def getrelations(uid1, uid2):
"""receive two user id, If they are friends, return 1, otherwise 0."""
dict_uid1 = getdict(uid1)
if uid2 in dict_uid1:
return 1
else:
return 0

绘制图像

利用以上函数判断好友关系并通过networkx创建一个相应的网络。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def getgraph(username, password):
"""Get the Graph Object and return it.
You must specify a Chinese font such as `SimHei` in ~/.matplotlib/matplotlibrc"""

uid = login(username, password)
dict_root = getdict(uid) # Get root tree

G = nx.Graph() # Create a Graph object
for uid1, uname1 in dict_root.items():
# Encode Chinese characters for matplotlib **IMPORTANT**
# if you want to draw Chinese labels,
uname1 = unicode(uname1, 'utf8')
G.add_node(uname1)
for uid2, uname2 in dict_root.items():
uname2 = unicode(uname2, 'utf8')
# Not necessary for networkx
if uid2 == uid1:
continue
if getrelations(uid1, uid2):
G.add_edge(uname1, uname2)

return G

最后是绘图函数,有很多控制图像输出的参数,可能多次调整才会得到想要的图像。在matplotlib画出的图像在窗口中也可以放大缩小选取适当范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def draw_graph(username, password, filename='graph.txt', label_flag=True, remove_isolated=True, different_size=True, iso_level=10, node_size=40):
"""Reading data from file and draw the graph.If not exists, create the file and re-scratch data from net"""
print "Generating graph..."
try:
with open(filename, 'r') as f:
G = p.load(f)
except:
G = getgraph(username, password)
with open(filename, 'w') as f:
p.dump(G, f)
#nx.draw(G)
# Judge whether remove the isolated point from graph
if remove_isolated is True:
H = nx.empty_graph()
for SG in nx.connected_component_subgraphs(G):
if SG.number_of_nodes() > iso_level:
H = nx.union(SG, H)
G = H
# Ajust graph for better presentation
if different_size is True:
L = nx.degree(G)
G.dot_size = {}
for k, v in L.items():
G.dot_size[k] = v
node_size = [G.dot_size[v] * 10 for v in G]
pos = nx.spring_layout(G, iterations=50)
nx.draw_networkx_edges(G, pos, alpha=0.2)
nx.draw_networkx_nodes(G, pos, node_size=node_size, node_color='r', alpha=0.3)
# Judge whether shows label
if label_flag is True:
nx.draw_networkx_labels(G, pos, alpha=0.5)
#nx.draw_graphviz(G)
plt.show()

return G

把以上函数写进一个文件比如说renren.py,在ipython中导入。

1
2
3
4
5
6
7
In[1]: from renren import *

In[2]: username = yourusername

In[3]: password = yourpassword

In[4]: draw_graph(username, password)

模糊化生成的好友关系图

总结

通过图像你会发现。这些绘图软件的算法相当不错的,你会发现很明显的聚类,这一片是大学同学、这片是小学初中同学,旁边与之联系紧密的是高中同学,这一片孤立的是网友等等。

也许你还会发现你的某些好友竟然相互认识。

抓取下来的数据还可以留待其它研究

又是横竖坐标都没的渣图

你也许会发现有的好友和你的共同好友多得超乎他人,也许发现共同好友分布比较均匀

就这么多这么简单。

希望你也能体会到这个乐趣横生的过程,对我来说,探索和学习的过程是相当意趣盎然的,折腾出来还相当有成就感呢。

What’s more

如果你想让matplotlib显示中文,你需要修改matplotlibrc更改字体。但有一种更通用的办法可以不用修改配置文件。自行google。

ps:这回开高亮了,没感觉和不高亮有啥大区别。感觉还是vim中的高亮漂亮啊,哪天不用pygments直接用vim converto html = =

FootNotes

[^1]:从来没用过api,搞不懂人人api,试着创建个应用Post过去结果认证失败,也没打算申请应用……总之不会搞= =

Visualize Shell History

注:代码写得很烂,不过感觉挺好玩所以写在这里。欢迎各路大牛指教。完整代码见github/reverland/scripts/tagcloud.py

曾经在linux用户中流行这么一个命令

~ ⮀ history | awk '{CMD[$2]++;count++;} END { for (a in CMD )print CMD[ a ]" " CMD[ a ]/count*100 "% " a }' | grep -v "./" | column -c3 -s " " -t |sort -nr | nl | head -n20 
    1  852  24.3012%    sudo
    2  376  10.7245%    pacman
    3  163  4.64917%    vim
    4  133  3.7935%     tsocks
    5  101  2.88078%    cd
    6  95   2.70964%    kill
    7  88   2.50998%    eix
    8  70   1.99658%    python2
    9  70   1.99658%    emerge
    10  69   1.96805%    ls
    11  63   1.79692%    git
    12  54   1.54022%    gcc
    13  51   1.45465%    pip2
    14  39   1.11238%    python
    15  37   1.05533%    pip
    16  37   1.05533%    nmap
    17  35   0.998289%   su
    18  32   0.912721%   xrandr
    19  31   0.884199%   rvm
    20  27   0.770108%   ssh

多cool的一个命令……我完全看不懂awk啥的……几天前看The practice of computing using python,上面讲到简单的文本处理和标签云,便想到把shell history用标签云的方式可视化出来。就是这样,我啥也不会。

先把shell历史定向到一个文件中吧,或者把zsh_history啥复制下

history > hist.txt

然后如何可视化呢?抱着有需求先搜寻有没有开源实现的想法找到了pytagcloud,稍加调整,生成的标签云相当漂亮:

oops!好大两个“异常词”,这么说一看我就是sudo党了,而且是经常滚的arch党……

为了看清更多细节,反映更多客观事实把这两个词去掉

看上去好多了………

pytagcloud还提供生成html数据的函数,你可以在线看看效果:

Demo

自己动手

其实自己实现类似的效果很简单,获得更明晰的理解和灵活性。

先给出以下要用的控制大小的参数,后面将直接用到它们。你可能需要多次调整来探索适合自己的数值,事实上,为了生成不同文本的标签云我试过了几十次。

boxsize = 600
basescale = 10
fontScale = 0.5
omitnumber = 5
omitlen = 0

文本处理

我们刚才保存的hits文件是这样的:

2480  pacman -Ss synap
2481  sudo pacman -S synaptiks
2482  synaptiks
2483  pypy
2484  vim pypy.py
2485  pypy pypy.py
2486  pip2 freeze
2487  pip2 freeze|grep flask
2488  pip2 install Flask
2489  pip2 install --upgrade Flask 

显然我们只要每行第二个词就行,这个任务很简单,我选择先将所有命令合并成一个大字符串,因为最开始我是直接用pytagcloud来生成标签云的,而它的示例代码用的是整个字符串:

def cmd2string(filename):
    '''accept the filename and return the string of cmd'''
    chist = []

    # Open the file and store the history in chist
    with open(filename, 'r') as f:
        chist = f.readlines()
        # print chist

    for i in range(len(chist)):
        chist[i] = chist[i].split()
        chist[i] = chist[i][1]
    ss = ''
    for w in chist:
        if w != 'sudo' and w != 'pacman':
            ss = ss + ' ' + w

    return ss

接着将字符串转换成字典,单词为键,出现次数为值:

def string2dict(string, dic):
    """split a string into a dict record its frequent"""
    wl = string.split()
    for w in wl:
        if w == '\n':  # 因为后来我看到中文分词结果中有\n...
            continue
        if w not in dic:
            dic[w] = 1
        else:
            dic[w] += 1
    return dic

接下来的两个函数来自之前我提到的那本书,稍微改动下让它在firefox18下正常显示,并且稍作美化,更改为随机的字体色彩和黑色背景。

这两个函数的含义是不言自明的,必要的html/css知识是需要的。[^1]

def makeHTMLbox(body, width):
    """takes one long string of words and a width(px) then put them in an HTML box"""
    boxStr = """<div style=\"width: %spx;background-color: rgb(0, 0, 0);border: 1px grey solid;text-align: center; overflow: hidden;\">%s</div>
    """
    return boxStr % (str(width), body)


def makeHTMLword(body, fontsize):
    """take words and fontsize, and create an HTML word in that fontsize."""
    #num = str(random.randint(0,255))
    # return random color for every tags
    color = 'rgb(%s, %s, %s)' % (str(random.randint(0, 255)), str(random.randint(0, 255)), str(random.randint(0, 255)))
    # get the html data
    wordStr = '<span style=\"font-size:%spx;color:%s;float:left;\">%s</span>'
    return wordStr % (str(fontsize), color, body)

Now, it’s time to get the proper html files of the tag cloud!

# get the html data first
wd = {}
s = cmd2string(filename)
wd = string2dict(s, wd)
vkl = [(k, v) for k, v in wd.items() if v >= omitnumber and len(k) > omitlen]  # kick off less used cmd
words = ""
for w, c in vkl:
    words += makeHTMLword(w, int(c * fontScale + basescale))   
html = makeHTMLbox(words, boxsize)
# dump it to a file
with open(filename.split('.')[0] + '.' + 'html', 'wb') as f:
    f.write(html)

将以上内容写到一个文件中,命名为比如说tagcloud.py

python2 tagcloud.py hist.txt # `import sys` and let filename = sys.argv[1]
# or `run tagcloud.py hist.txt` in ipython

看看效果吧:

genkernelsystemctlnengo-cl./nengotsocksaxelnamcappidgingroupsemergefirefoxnmapvim/opt/nengo/nengocatmvhistorynengotargccdmesg|egrepmansupingmakepkglspythonrakermpkgfilenetstatechotopmkdirjohngvimpip2xrandrdfsshgemipython2ipythonmodprobeyou-getkdesuchmodcdcppslsmodeixmd5sumvimakepactreefc-list|grepgdbkillsourcelspcijekyllpython2exportwkhtmltopdfloginctlkatawa-shoujopkillgiteselectdebug-flvrvmpipequery

相当棒不是么?[^2]

十八大报告标签云

为了深刻领会党的十八大大会精神^3,我做了下我党的十八大报告标签云:

先从网上找到总书记的十八大报告全文,保存为shibada.txt:

中文分词有个相当不错的python库jieba

将单词保存到一个字典中

import jieba

with open('shibada.txt','r') as f:
    s = f.read()
wg = jieba.cut(s, cut_all=True)
wd = {}
for w in wg:
    if w not in wd:
        wd[w] = 1
    else:
        wd[w] += 1

生成html数据:

for w, c in vkl:
        words += makeHTMLword(w, int(c * fontScale + basescale))   
html = makeHTMLbox(words, boxsize)
htmlzh = unicode.encode(html,'UTF-8')  # Important!
# dump it to a file
with open(filename.split('.')[0] + '.' + 'html', 'wb') as f:
    f.write(htmlzh)
金融机构个人账户体制改革全党全国举世公认学前教育贸易组织管理网络精神食粮党外人士政企分开推进改革人民满意科学素养行动指南创建活动保护环境对外贸易党员干部充分发挥中小城市利用效率高度重视立于不败之地香港特别行政区资源整合社会制度科教兴国建立健全群众体育基本路线核心作用劳有所得腐败现象充满活力强权政治检察机关人大代表医药卫生居民消费各个环节坚持真理政治协商德智体美理想信念战略思想伸张正义组织协调思想道德两岸关系跨国公司腐败问题宏伟目标发展壮大不断扩大党风廉政共同富裕穷兵黩武根本宗旨管理科学急难险重医疗保障官僚主义严峻考验教师队伍保持稳定医疗卫生独立自主重大突破持久和平提高效益年龄结构合情合理妥善处置团结奋斗联系群众产业政策保险制度指导思想文化素质贫穷落后党员队伍依法行政睦邻友好各族人民巨大成就明显增强公共安全农民收入巩固国防恐怖主义主义精神主义教育执政为民机构编制妄自菲薄澳人治澳同舟共济群众反映基础产业经贸关系军事力量周边国家发展中国家综合国力求真务实批评和自社会效益民主监督马克思列安全事故事业单位建设工程改革开放土地增值解放思想产业结构违反纪律千辛万苦激励机制全面提高领导班子初级阶段出色完成自由贸易本质属性道德素质市场主体任人唯贤理论体系科学决策妇女儿童公立医院节能降耗人口老龄化贫困地区警钟长鸣坚定信心民主集中制创新能力日益增长时代特征经济体制国民经济气候变化教育引导成绩显著明显改善严惩不贷根本任务级计算机才尽其用党的建设法制建设党和人民基本国策基础设施严重困难预防为主基本原理养老保险面向世界国有经济政令畅通爱国卫生一律平等国际竞争社会保险自由贸易区腐败体系荣辱与共中型企业年轻干部互联互通五湖四海优秀人才伟大旗帜政策措施国家机关人人平等社会保障共同愿望始终不渝妥善处理深化改革综合治理祖国统一国际形势勇于创新现代农业勤俭节约民主党派统筹规划稳步前进价值体系群众意见中华文化长期实践集体主义山清水秀任务艰巨党的领导澳门特别行政区常住人口发展党员亿万人民团结互助人力资源矿产资源百花齐放引咎辞职中国共产党工作人员物质基础收入水平东北地区革命军人诉诸武力服务设施统一战线严格执法执法必严主要矛盾上层建筑分配制度优良传统结构调整全党同志退休干部政府职能百家争鸣波澜壮阔服务大局社会治安公平正义坚强有力不失时机坚持不懈公共卫生根本保证金融危机充分调动五项原则与时俱进非公有制食品药品党政领导面向未来高新技术民族团结民主制度素质教育繁荣富强恶化趋势小康社会义务教育以言代法争取和平社会福利技术创新常抓不懈经济总量和平时期服务水平扎扎实实前所未有下有对策优秀干部法定程序民主决策伟大胜利安居乐业各界人士公共财政共同奋斗空间结构纪检监察差额选举血脉相连城乡规划首创精神管理机制简政放权身心健康社会安定各尽所能家庭财产路线教育代表大会少数民族政权机关紧密结合人口素质热点问题一心一意违法犯罪大力发展贯彻落实人与自然十分艰巨教育资源感天动地水土流失谦虚谨慎信息技术形式主义公共服务海洋权益能源安全舆论监督集体经济意识形态考核办法决策程序正反两方时代精神内政外交廉政建设进一步提高严格执行武器装备历史使命经济命脉列宁主义总体布局子孙后代精神实质历史进程革命先烈社会变革和平解决创先争优积极支持再生能源从根本上自我教育教书育人坚定信念无党派人士人民代表品德教育优先发展劳动生产率规模经营以人为本领土完整基层组织立党为公自然灾害一如既往坚定不移改旗易帜国家主权领导职务加强监督公益事业医疗保险生产总值海外侨胞积极向上经营机制艰苦奋斗重中之重有用之才新闻出版民主评议不良风气社区卫生军事训练台湾同胞可持续性多种形式丰富多彩必然选择自然生态防御能力经济基础重大成就和睦相处人口比例民族特色努力实现科学研究全面实施正当权益组织生活十国集团承包经营国防建设合法收入文化产业明显提高武装警察知识分子相信群众扶贫开发社会阶层妥善解决自我批评金融体系不断丰富取长补短劳动所得全国代表大会一府两院脱离群众分配比例提高质量工作力度惩治腐败美好世界始终保持工人阶级城乡居民高度一致社会公德技术产业金融监管政治文明国际争端党纪国法遵守纪律宏观调控产权保护组成部分深入开展生态效益广大青年老有所养戒骄戒躁弱肉强食团结合作超级计算机消化吸收和衷共济行政长官有利条件兢兢业业精神力量攻坚克难在思想上长治久安人人有责文化交流职业道德同心同德对外开放国民收入关键在于加以解决教育方针国有资本不断加强宏观经济不可逆转审判机关生命财产万众一心千方百计生产关系广泛开展干部队伍矢志不渝国有企业内忧外患公民道德高度自治权面向基层友好往来合法权益功能定位没有特权战略方针网络空间能进能出人民调解社会主义伟大工程议事规则外交政策三个代表人才培养居民收入伟大事业集中统一中央政府军事战略科技进步新民主主义埋头苦干德才兼备特殊教育产品质量居安思危马克思列宁主义民主协商精神文明福利制度正确方向步调一致爱国人士信息系统徇私枉法绝不允许长效机制低收入者充满希望港人治港特殊人群正确处理慈善事业北京奥运工资制度发达国家参政议政党的基本民主选举妄自尊大积极主动政治立场全面落实方针政策市场规律严峻形势毫不动摇经济效益节约资源科学知识尊重人才突出位置群众利益和平共处人民团体认真总结环境污染力量对比路线方针中共中央统筹兼顾法制宣传马克思主义霸权主义新民主主义革命有令不行有禁不止地方党委基本纲领后备力量安全监管正确认识集中精力新民主主党和政府选拔干部公正司法文学艺术管理体系自我表现全心全意邓小平理论一国两制环境友好当今世界第十七届合理配置收益分配奋发有为管理体制集中体现领导核心大是大非管理制度民间团体退役军人社会科学化解矛盾生活空间国际事务调整机制当家作主贴近生活服务体系积贫积弱军民团结人民军队信息网络五位一体病有所医战略规划中央委员会生产能力和谐社会根本利益衣食住行商业模式清醒认识实事求是男女平等能源消耗科学合理按劳分配依靠人民切身利益紧紧抓住共同努力违法必究整体实力中央委员水利建设生态系统优化结构相互交织实际行动总揽全局税收制度提高素质人事制度市场竞争明确提出积极开展决不允许依法治国干涉主义共同理想敌对势力非法收入中华儿女局部战争干部人事奢侈浪费突发事件经济社会组织体制保障体系切实有效行政区划奋勇前进伙伴关系流动人口国防科技开发利用公共事务外部环境职工代表中华民族坚实基础清正廉洁保障制度前进方向反腐倡廉充分证明人均收入经验教训根本途径不败之地依靠群众现实意义积极参与共同利益杰出贡献上有政策民主集中互相监督群众路线职业技能共产主义和平统一新形势下九十多年十分复杂党和国家是非曲直关键环节统一大业产品价格仁人志士精神家园领导集体突如其来新兴产业重大进展自我完善犯罪活动一以贯之风云变幻勇于进取全面实现资源节约型忧患意识高速铁路信息安全内部矛盾青年工人户籍制度深入人心逐步完善广大党员问政于民二氧化碳政治体制社会风尚五千多年传统产业三十多年廉政文化模范作用民主主义有法必依自我管理损害赔偿幸福美好坚决贯彻时代特色国际金融严格控制自觉遵守文明执法建言献策财政收入基本保障拒腐防变永无止境宣传教育健康成长民主权利卫生事件聚精会神党政干部计划生育重大胜利长期共存生活富裕循序渐进求同存异主义理论分配机制增强党性成效显著粮食安全民主法制立于不败软弱涣散大案要案优势互补教育领域保护主义民族之林基本国情奋发进取世界贸易组织肝胆相照多党合作毛泽东思想行使权力前沿技术不懈努力海洋资源投机钻营者知识产权友好合作人才资源顽强拼搏人类文明利益冲突物质文明科学规范良好环境社区服务尊重知识第十八次市场调节民族区域思想观念市场经济公共资源大有作为赔偿制度从严治党爱国主义作出贡献科技体制合理安排基本方针规模宏大放在首位扫黄打非人民政协积极响应高级干部艰苦卓绝传统美德资源管理精神支柱金融体制劳动报酬精神状态标本兼治利用外资边疆地区语言文字终身教育爱国统一战线服务网络党的纪律资源配置各得其所充分体现阔步前进人民代表大会生产方式开展批评安置工作扩大内需必由之路国际局势此时此刻共产党人合理布局生态环境环境保护发展前景网络安全亡党亡国政法队伍人才队伍健身运动

嗯……深刻领会了十八大精神^4

What’s more

可以将标签云移植到博客上。

Footnotes

[^1]:抱歉……我忘记python怎么将长行划分了\ or \\?
[^2]:emerge 乱入……

基于开源工具的数据分析

书的图片

这是书评,不仅仅是书评。事实上,这本书我还没看完。考研之前大概两个月时偶然在图书馆外文图书阴暗的角落发现了这本书,然后考研复习平常上课都被我扔了,连续一周徜徉在数学和计算机的世界里。再往后?大概到十四还是十五章被迫准备研究生入学考试去,直到现在还没拾起来。

后来在一楼看到有中文图书,感觉翻译的不错,起码比我自己理解的好。就换了本中文的带回家来看。

废话很多?不是么?好吧这篇书评和书的内容没什么大的关联,满篇只因为我又想 胡扯 了。

Amazon上对这本书的评价:Very Insight。Too insight 以致于我真想跑跑题。

系统与文化

从书上的一个故事说起。作者建议数据分析工作人员和科学家用Unix系统工作,为此专门举了个例子:

True story: I needed to send a file containing several millions of keys to a coworker.(The company did not work on Unix.) Since the file was too large to fit safely into an email message, I posted it to a web server on my desktop and sent my coworker the link. (I dutifully had provided the file with the extension .txt, so that he would be able to open it.) Five minutes later, he calls me back: “I can’t open that”—“What do you mean?”—“Well, I click the link, but ScrapPaper [the default text editor for small text files on this particular system] dies because the file is too big.” This coworker was not inept (in fact, he was quite good at his primary job), but he displayed the particular non-problem-solving attitude that develops in predefined work environments: “Link, click.” It did not even occur to him to think of something else to try. That’s a problem!

就在昨天,看到行者无疆上对百度的公司的风格很是一顿猛批:其为员工预装的系统还是十年前的windows XP,天天用着盗版SecureCRT和Office软件工作。问题是:This matters?

我觉得重要!关键在习惯和文化上!

看看作者如何谈论:

Unix was developed for precisely this kind of ad hoc programming with files
and data, and it continues to provide the most liberating environment for such work.
Unix (and its variants, including Linux and Mac OS X) has some obvious technical
advantages, but its most important property in the present context is that it encourages
you to devise solutions. It does not try (or pretend) to do the job for you, but it goes out of
its way to give you tools that you might find handy—without prescribing how or for
what you use them. In contrast, other operating systems tend to encourage you to stay
within the boundaries of certain familiar activity patterns—which does not encourage
the development of your problem-solving abilities (or, more importantly, your
problem-solving attitudes).

关键就在这里,Unix系统 encourages you to devise solutions, 它不尝试为你做完所有事,但是它竭尽所能为你提供各种各样的工具和选择,而不是规定使用的方式和用途。而其它操作系统往往鼓励你留在习惯的活动模式范围内——毫不鼓励你提高解决问题的能力,更重要的是,解决问题的态度!

这也就是为什么,Linux、Mac OS和BSD这些Unix系统被称作黑客^1的操作系统,而Windows一直饱受诟病,在geek的群体中备受歧视。

从开始接触Linux到现在不到一年半,按理说作为一个noob没什么评头论足的资格,但这一年半却是改变我人生轨迹的一年半,我想分享出来自己的想法和折腾。也为了印证为何我也这么说这么想。

从自己说起

一年半时间,世界和自我完全变了。

折腾的苦与乐,爱与恨,交织纷繁。

也许当初真的太闲了才会一脚踏进这个无底洞,不过,who cares。就说说这个系统和文化如何影响、塑造了我。

故事从SAS开始,一款统计软件。当时数学建模用着盗版的SAS,老师说,这个正版太贵,我们买不起。

问题是有多贵?我开始上百度^2找答案,听说的答案是:一年几十万。

The price shocked me,从来没想过一个软件会这么贵。缘分就在这里,看到有人在推荐R(A powerful statistics environment)作为替代。这是我第一次听说开源,也是我折腾的开始,踏入新天地的开端。

没体会过的人永远不会理解我为什么说新天地,也不会理解这有什么好感慨的。

我从来没想过将来去从事什么计算机相关的工作,从来。从来觉得数学这东西一无是处,都是空谈。从来觉得世界分工很多事情自己都不该去接触去学习。从来以为世界就是自己身边这么大。从来觉得一生这么混混就过去了。

但一年半后我再也不这样想了。依然迷茫,然而有信心。

这一年半我觉得做了许多让自己骄傲的事,最起码没有虚度。虽然在别人眼里我碌碌无为,甚至一无是处。我依然相信、我的所有努力和折腾不会白费。就像Jobs当在Stanford演讲所说[^3]。

一分耕耘、一分收获。

为什么会折腾?和系统有关系吗?

有!文化。有些环境不鼓励探索和设计解决问题,而是鼓励你混日子。

关于开源社区的文化,我想说几条:

热情

大多数人都只因热爱。开源社区是充满热情与爱的社区,开源本身就是个奇迹。你会想到有人无偿将自己的劳动成果贡献出来让所有人都能自由获取和使用吗?然而不光有人做,还有一群人做。他们把技术、心得分享出来,让每个想学习的人都平等的接触他们。他们花费宝贵时间写出组织良好充满热情的文档、教程,录制视频,他们花费宝贵的时间本地化各种应用,在IRC和论坛等地热情帮助他人。他们也做出各种各样的艺术品。

分享

开源社区的分享氛围真是太好了。我为什么倾向于使用Python,因为它本身就是依靠开源发展壮大起来,深受开源文化陶冶的语言。来自各个领域、各个地区的爱好者们开发了各种各样的库分享出来让每个人都能自由获取,以致于很多事情用Python太简单了。数以万计的开源软件使用者在互联网上分享、反馈、改进他们喜欢的东西。这种文化也构成了互联网的分享基础。

好奇心

正如本书作者在前言中所说:

All You need is just CURIOSITY!

开源社区给了每个人自由平等接触学习的机会,你所需要的仅仅是好奇心。从接触linux开始,我在上面折腾过各种数学软件R、octave、scilab、ipython(其实这不是……)、各种风格和特色语言[^4]、自己学习图像处理的基础知识和使用图像处理软件(如果时间很多就去学blender)[^5]、自己学习字体排版的理论折腾LaTeX、自己试着零基础折腾各种动静态网站、在虚拟机上装好各种linux/windows系统然后连着玩……虽然没学到什么或者学过也忘了或者不深入,但学到的是解决问题的态度和能力,这才是我觉得最重要的。时光飞逝,一切都会过时变化,重要的是面对问题的态度和解决问题的能力。

开源的文化改变了我的思想、行动,开阔了我的眼界,我觉得这对每个人都是有益的。剩下的,就是你愿不愿意付出“代价”来改变自己了。

开放的社区欢迎任何人投入其中、分享、学习、进步,接触和尝试各个领域,你也许对计算机知之甚少,对数学一窍不通,但行业的鸿沟真的这么明显吗?难而不会、会而不难,你会找到很多开放的社区有热情的人分享的资料文章、看到清晰热情的文档、在论坛IRC等获得热心的帮助。

不妨试试,你会爱上这种文化。

最后顺便说下,开源社区创造了很多很棒的东西,越是重要的东西越默默无闻,然而不可或缺。

Footnotes

[^3]:Follow U heart.
[^4]:其实很少,我认真学的第一门语言是common lisp。
[^5]:有兴趣的同学可以去看看Tears of Steel,这是blender、gimp、Inkscape等开源多媒体软件制作的。